1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer: Tim Pope <vimNOSPAM@tpope.org>
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
6 if exists('g:loaded_fugitive') || &cp
9 let g:loaded_fugitive = 1
11 if !exists('g:fugitive_git_executable')
12 let g:fugitive_git_executable = 'git'
17 function! s:function(name) abort
18 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
21 function! s:sub(str,pat,rep) abort
22 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
25 function! s:gsub(str,pat,rep) abort
26 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
29 function! s:shellesc(arg) abort
30 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
32 elseif &shell =~# 'cmd' && a:arg !~# '"'
35 return shellescape(a:arg)
39 function! s:fnameescape(file) abort
40 if exists('*fnameescape')
41 return fnameescape(a:file)
43 return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
47 function! s:throw(string) abort
48 let v:errmsg = 'fugitive: '.a:string
56 let v:warningmsg = a:str
59 function! s:shellslash(path)
60 if exists('+shellslash') && !&shellslash
61 return s:gsub(a:path,'\\','/')
67 function! s:add_methods(namespace, method_names) abort
68 for name in a:method_names
69 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
74 function! s:command(definition) abort
75 let s:commands += [a:definition]
78 function! s:define_commands()
79 for command in s:commands
80 exe 'command! -buffer '.command
84 function! s:compatibility_check()
85 if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning')
86 let g:fugitive_did_compatibility_warning = 1
87 call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim")
91 augroup fugitive_utility
93 autocmd User Fugitive call s:define_commands()
94 autocmd VimEnter * call s:compatibility_check()
97 let s:abstract_prototype = {}
100 " Initialization {{{1
102 function! s:ExtractGitDir(path) abort
103 let path = s:shellslash(a:path)
104 if path =~? '^fugitive://.*//'
105 return matchstr(path,'fugitive://\zs.\{-\}\ze//')
107 let fn = fnamemodify(path,':s?[\/]$??')
111 if filereadable(fn . '/.git/HEAD')
112 return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','')
113 elseif fn =~ '\.git$' && filereadable(fn . '/HEAD')
114 return s:sub(simplify(fnamemodify(fn,':p')),'\W$','')
117 let fn = fnamemodify(ofn,':h')
122 function! s:Detect(path)
123 if exists('b:git_dir') && b:git_dir ==# ''
126 if !exists('b:git_dir')
127 let dir = s:ExtractGitDir(a:path)
132 if exists('b:git_dir')
133 silent doautocmd User Fugitive
134 cnoremap <expr> <buffer> <C-R><C-G> fugitive#buffer().rev()
135 let buffer = fugitive#buffer()
136 if expand('%:p') =~# '//'
137 call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)',''))
139 if b:git_dir !~# ',' && stridx(buffer.getvar('&tags'),b:git_dir.'/tags') == -1
141 call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/'.&filetype.'.tags')
143 call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/tags')
150 autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
151 autocmd FileType netrw call s:Detect(expand('<afile>:p'))
152 autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
153 autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_restore')
159 let s:repo_prototype = {}
162 function! s:repo(...) abort
163 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p')))
165 if has_key(s:repos,dir)
166 let repo = get(s:repos,dir)
168 let repo = {'git_dir': dir}
169 let s:repos[dir] = repo
171 return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep')
173 call s:throw('not a git repository: '.expand('%:p'))
176 function! s:repo_dir(...) dict abort
177 return join([self.git_dir]+a:000,'/')
180 function! s:repo_tree(...) dict abort
182 let dir = fnamemodify(self.git_dir,':h')
183 return join([dir]+a:000,'/')
185 call s:throw('no work tree')
188 function! s:repo_bare() dict abort
189 return self.dir() !~# '/\.git$'
192 function! s:repo_translate(spec) dict abort
193 if a:spec ==# '.' || a:spec ==# '/.'
194 return self.bare() ? self.dir() : self.tree()
195 elseif a:spec =~# '^/'
196 return fnamemodify(self.dir(),':h').a:spec
197 elseif a:spec =~# '^:[0-3]:'
198 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
199 elseif a:spec ==# ':'
200 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE)
201 return fnamemodify($GIT_INDEX_FILE,':p')
203 return self.dir('index')
205 elseif a:spec =~# '^:/'
206 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
207 return 'fugitive://'.self.dir().'//'.ref
208 elseif a:spec =~# '^:'
209 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
210 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
211 return self.dir(a:spec)
212 elseif filereadable(s:repo().dir('refs/'.a:spec))
213 return self.dir('refs/'.a:spec)
214 elseif filereadable(s:repo().dir('refs/tags/'.a:spec))
215 return self.dir('refs/tags/'.a:spec)
216 elseif filereadable(s:repo().dir('refs/heads/'.a:spec))
217 return self.dir('refs/heads/'.a:spec)
218 elseif filereadable(s:repo().dir('refs/remotes/'.a:spec))
219 return self.dir('refs/remotes/'.a:spec)
220 elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD'))
221 return self.dir('refs/remotes/'.a:spec,'/HEAD')
224 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
225 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
226 return 'fugitive://'.self.dir().'//'.ref.path
228 return self.tree(a:spec)
233 call s:add_methods('repo',['dir','tree','bare','translate'])
235 function! s:repo_git_command(...) dict abort
236 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
237 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
240 function! s:repo_git_chomp(...) dict abort
241 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
244 function! s:repo_git_chomp_in_tree(...) dict abort
245 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
248 execute cd.'`=s:repo().tree()`'
249 return call(s:repo().git_chomp, a:000, s:repo())
255 function! s:repo_rev_parse(rev) dict abort
256 let hash = self.git_chomp('rev-parse','--verify',a:rev)
257 if hash =~ '\<\x\{40\}$'
258 return matchstr(hash,'\<\x\{40\}$')
260 call s:throw('rev-parse '.a:rev.': '.hash)
263 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
265 function! s:repo_dirglob(base) dict abort
266 let base = s:sub(a:base,'^/','')
267 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
268 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
272 function! s:repo_superglob(base) dict abort
273 if a:base =~# '^/' || a:base !~# ':'
276 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
277 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
278 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
282 let base = s:sub(a:base,'^/','')
283 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
284 call map(matches,'s:shellslash(v:val)')
285 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
286 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
287 let results += matches
291 elseif a:base =~# '^:'
292 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
293 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
294 if a:base !~# '^:[0-3]\%(:\|$\)'
295 call filter(entries,'v:val[1] == "0"')
296 call map(entries,'v:val[2:-1]')
298 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
302 let tree = matchstr(a:base,'.*[:/]')
303 let entries = split(self.git_chomp('ls-tree',tree),"\n")
304 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
305 call map(entries,'tree.s:sub(v:val,".*\t","")')
306 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
310 call s:add_methods('repo',['dirglob','superglob'])
312 function! s:repo_keywordprg() dict abort
313 let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show'
314 if has('gui_running') && !has('win32')
315 return g:fugitive_git_executable . ' --no-pager' . args
317 return g:fugitive_git_executable . args
321 call s:add_methods('repo',['keywordprg'])
326 let s:buffer_prototype = {}
328 function! s:buffer(...) abort
329 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
330 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
331 if buffer.getvar('git_dir') !=# ''
334 call s:throw('not a git repository: '.expand('%:p'))
337 function! fugitive#buffer(...) abort
338 return s:buffer(a:0 ? a:1 : '%')
341 function! s:buffer_getvar(var) dict abort
342 return getbufvar(self['#'],a:var)
345 function! s:buffer_setvar(var,value) dict abort
346 return setbufvar(self['#'],a:var,a:value)
349 function! s:buffer_getline(lnum) dict abort
350 return getbufline(self['#'],a:lnum)[0]
353 function! s:buffer_repo() dict abort
354 return s:repo(self.getvar('git_dir'))
357 function! s:buffer_type(...) dict abort
358 if self.getvar('fugitive_type') != ''
359 let type = self.getvar('fugitive_type')
360 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
362 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
364 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
366 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
368 elseif isdirectory(self.spec())
369 let type = 'directory'
370 elseif self.spec() == ''
372 elseif filereadable(self.spec())
378 return !empty(filter(copy(a:000),'v:val ==# type'))
386 function! s:buffer_spec() dict abort
387 let bufname = bufname(self['#'])
389 for i in split(bufname,'[^:]\zs\\')
390 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
392 return s:shellslash(fnamemodify(retval,':p'))
397 function! s:buffer_spec() dict abort
398 let bufname = bufname(self['#'])
399 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
404 function! s:buffer_name() dict abort
408 function! s:buffer_commit() dict abort
409 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
412 function! s:buffer_path(...) dict abort
413 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
415 let rev = s:sub(rev,'\w*','')
417 let rev = self.spec()[strlen(self.repo().tree()) : -1]
419 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
422 function! s:buffer_rev() dict abort
423 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
425 return ':'.rev[0].':'.rev[2:-1]
427 return s:sub(rev,'/',':')
428 elseif self.spec() =~ '\.git/index$'
430 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
431 return self.spec()[strlen(self.repo().dir())+1 : -1]
437 function! s:buffer_sha1() dict abort
438 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
439 return self.repo().rev_parse(self.rev())
445 function! s:buffer_expand(rev) dict abort
446 if a:rev =~# '^:[0-3]$'
447 let file = a:rev.self.path(':')
448 elseif a:rev =~# '^[-:]/$'
449 let file = '/'.self.path()
450 elseif a:rev =~# '^-'
451 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
452 elseif a:rev =~# '^@{'
453 let file = 'HEAD'.a:rev.self.path(':')
454 elseif a:rev =~# '^[~^]'
455 let commit = s:sub(self.commit(),'^\d=$','HEAD')
456 let file = commit.a:rev.self.path(':')
460 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
463 function! s:buffer_containing_commit() dict abort
464 if self.commit() =~# '^\d$'
466 elseif self.commit() =~# '.'
473 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit'])
478 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
480 function! s:ExecuteInTree(cmd) abort
481 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
484 execute cd.'`=s:repo().tree()`'
491 function! s:Git(bang,cmd) abort
492 let git = s:repo().git_command()
493 if has('gui_running') && !has('win32')
494 let git .= ' --no-pager'
496 let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
497 call s:ExecuteInTree('!'.git.' '.cmd)
498 call fugitive#reload_status()
499 return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
502 function! s:GitComplete(A,L,P) abort
503 if !exists('s:exec_path')
504 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
506 let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
507 if a:L =~ ' [[:alnum:]-]\+ '
508 return s:repo().superglob(a:A)
512 return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
519 function! s:DirComplete(A,L,P) abort
520 let matches = s:repo().dirglob(a:A)
524 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :cd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
525 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :lcd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
530 call s:command("-bar Gstatus :execute s:Status()")
532 function! s:Status() abort
536 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
538 return 'echoerr v:errmsg'
543 function! fugitive#reload_status() abort
544 let mytab = tabpagenr()
545 for tab in [mytab] + range(1,tabpagenr('$'))
546 for winnr in range(1,tabpagewinnr(tab,'$'))
547 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
548 execute 'tabnext '.tab
550 execute winnr.'wincmd w'
555 call s:BufReadIndex()
558 if exists('restorewinnr')
561 execute 'tabnext '.mytab
568 function! s:StageDiff(...) abort
569 let cmd = a:0 ? a:1 : 'Gdiff'
570 let section = getline(search('^# .*:$','bnW'))
571 let line = getline('.')
572 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
573 if filename ==# '' && section == '# Changes to be committed:'
574 return 'Git diff --cached'
575 elseif filename ==# ''
577 elseif line =~# '^#\trenamed:' && filename =~ ' -> '
578 let [old, new] = split(filename,' -> ')
579 execute 'Gedit '.s:fnameescape(':0:'.new)
580 return cmd.' HEAD:'.s:fnameescape(old)
581 elseif section == '# Changes to be committed:'
582 execute 'Gedit '.s:fnameescape(':0:'.filename)
585 execute 'Gedit '.s:fnameescape('/'.filename)
590 function! s:StageToggle(lnum1,lnum2) abort
593 for lnum in range(a:lnum1,a:lnum2)
594 let line = getline(lnum)
596 if line ==# '# Changes to be committed:'
597 call repo.git_chomp_in_tree('reset','-q')
600 if !search('^# Untracked files:$','W')
601 call search('^# Change','W')
604 elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
605 call repo.git_chomp_in_tree('add','-u')
608 if !search('^# Untracked files:$','W')
609 call search('^# Change','W')
612 elseif line ==# '# Untracked files:'
613 " Work around Vim parser idiosyncrasy
614 call repo.git_chomp_in_tree('add','-N','.')
617 if !search('^# Change\%(d but not updated\|s not staged for commit\):$','W')
618 call search('^# Change','W')
622 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (\a\+ [[:alpha:], ]\+)\)\=$')
626 if !exists('first_filename')
627 let first_filename = filename
630 let section = getline(search('^# .*:$','bnW'))
631 if line =~# '^#\trenamed:' && filename =~ ' -> '
632 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
633 let filename = cmd[-1]
634 elseif section =~? ' to be '
635 let cmd = ['reset','-q','--',filename]
636 elseif line =~# '^#\tdeleted:'
637 let cmd = ['rm','--',filename]
639 let cmd = ['add','--',filename]
641 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
643 if exists('first_filename')
644 let jump = first_filename
645 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
646 if f !=# '' | let jump = f | endif
647 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
648 if f !=# '' | let jump = f | endif
652 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( (new commits)\)\=\$','W')
654 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
656 return 'echoerr v:errmsg'
661 function! s:StagePatch(lnum1,lnum2) abort
665 for lnum in range(a:lnum1,a:lnum2)
666 let line = getline(lnum)
667 if line ==# '# Changes to be committed:'
668 return 'Git reset --patch'
669 elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
670 return 'Git add --patch'
672 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
676 if !exists('first_filename')
677 let first_filename = filename
680 let section = getline(search('^# .*:$','bnW'))
681 if line =~# '^#\trenamed:' && filename =~ ' -> '
682 let reset += [split(filename,' -> ')[1]]
683 elseif section =~? ' to be '
684 let reset += [filename]
685 elseif line !~# '^#\tdeleted:'
686 let add += [filename]
691 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
694 execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
696 if exists('first_filename')
700 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( (new commits)\)\=\$','W')
703 return 'echoerr v:errmsg'
711 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
713 function! s:Commit(args) abort
714 let old_type = s:buffer().type()
715 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
717 let msgfile = s:repo().dir('COMMIT_EDITMSG')
718 let outfile = tempname()
719 let errorfile = tempname()
721 execute cd.'`=s:repo().tree()`'
724 let old_editor = $GIT_EDITOR
725 let $GIT_EDITOR = 'false'
727 let command = 'env GIT_EDITOR=false '
729 let command .= s:repo().git_command('commit').' '.a:args
731 silent execute '!('.command.' > '.outfile.') >& '.errorfile
732 elseif a:args =~# '\%(^\| \)--interactive\>'
733 execute '!'.command.' 2> '.errorfile
735 silent execute '!'.command.' > '.outfile.' 2> '.errorfile
738 if filereadable(outfile)
739 for line in readfile(outfile)
745 let errors = readfile(errorfile)
746 let error = get(errors,-2,get(errors,-1,'!'))
747 if error =~# '\<false''\=\.$'
749 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
750 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
751 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
752 let args = '-F '.s:shellesc(msgfile).' '.args
753 if args !~# '\%(^\| \)--cleanup\>'
754 let args = '--cleanup=strip '.args
756 let old_nr = bufnr('')
757 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
760 keepalt split `=msgfile`
762 if old_type ==# 'index'
763 execute 'bdelete '.old_nr
765 let b:fugitive_commit_arguments = args
766 setlocal bufhidden=delete filetype=gitcommit
775 return 'echoerr v:errmsg'
777 if exists('old_editor')
778 let $GIT_EDITOR = old_editor
781 call delete(errorfile)
783 call fugitive#reload_status()
787 function! s:CommitComplete(A,L,P) abort
788 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
789 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=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--template=', '--untracked-files', '--verbose']
790 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
792 return s:repo().superglob(a:A)
796 function! s:FinishCommit()
797 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
799 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
800 return s:Commit(args)
805 augroup fugitive_commit
807 autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
813 if !exists('g:fugitive_summary_format')
814 let g:fugitive_summary_format = '%s'
817 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)")
818 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
820 function! s:Grep(bang,arg) abort
821 let grepprg = &grepprg
822 let grepformat = &grepformat
823 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
826 execute cd.'`=s:repo().tree()`'
827 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
828 let &grepformat = '%f:%l:%m'
829 exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
830 let list = getqflist()
832 if bufname(entry.bufnr) =~ ':'
833 let entry.filename = s:repo().translate(bufname(entry.bufnr))
835 elseif a:arg =~# '\%(^\| \)--cached\>'
836 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
840 call setqflist(list,'r')
841 if !a:bang && !empty(list)
842 return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
844 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
847 let &grepprg = grepprg
848 let &grepformat = grepformat
853 function! s:Log(cmd,...)
854 let path = s:buffer().path('/')
855 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
858 let cmd = ['--no-pager', 'log', '--no-color']
859 let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')]
860 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
861 if s:buffer().commit() =~# '\x\{40\}'
862 let cmd += [s:buffer().commit()]
863 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
864 let cmd += [s:buffer().path()[5:-1]]
867 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
869 let cmd += ['--',path[1:-1]]
871 let grepformat = &grepformat
872 let grepprg = &grepprg
873 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
876 execute cd.'`=s:repo().tree()`'
877 let &grepprg = call(s:repo().git_command,cmd,s:repo())
878 let &grepformat = '%f::%m'
881 let &grepformat = grepformat
882 let &grepprg = grepprg
888 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
890 function! s:Edit(cmd,...) abort
894 let file = s:buffer().expand(a:1)
895 elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>'
896 let file = s:buffer().path(':')
898 let file = s:buffer().path('/')
901 let file = s:repo().translate(file)
903 return 'echoerr v:errmsg'
906 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
908 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
911 return a:cmd.' '.s:fnameescape(file)
915 function! s:EditComplete(A,L,P) abort
916 return s:repo().superglob(a:A)
919 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',<f-args>)")
920 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',<f-args>)")
921 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit :execute s:Edit('pedit<bang>',<f-args>)")
922 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit :execute s:Edit('split<bang>',<f-args>)")
923 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit :execute s:Edit('vsplit<bang>',<f-args>)")
924 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit<bang>',<f-args>)")
925 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read<bang>',<f-args>)")
930 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
931 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
932 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
934 function! s:Write(force,...) abort
935 if exists('b:fugitive_commit_arguments')
936 return 'write|bdelete'
937 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
939 elseif s:buffer().type() == 'index'
942 let mytab = tabpagenr()
943 let mybufnr = bufnr('')
944 let path = a:0 ? a:1 : s:buffer().path()
946 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
948 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
949 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) !=# ''
950 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
951 return 'echoerr v:errmsg'
953 let file = s:repo().translate(path)
955 for nr in range(1,bufnr('$'))
956 if fnamemodify(bufname(nr),':p') ==# file
961 if treebufnr > 0 && treebufnr != bufnr('')
962 let temp = tempname()
963 silent execute '%write '.temp
964 for tab in [mytab] + range(1,tabpagenr('$'))
965 for winnr in range(1,tabpagewinnr(tab,'$'))
966 if tabpagebuflist(tab)[winnr-1] == treebufnr
967 execute 'tabnext '.tab
969 execute winnr.'wincmd w'
975 silent execute '$read '.temp
976 silent execute '1,'.last.'delete_'
981 if exists('restorewinnr')
984 execute 'tabnext '.mytab
990 call writefile(readfile(temp,'b'),file,'b')
993 execute 'write! '.s:fnameescape(s:repo().translate(path))
997 let error = s:repo().git_chomp_in_tree('add', '--force', file)
999 let error = s:repo().git_chomp_in_tree('add', file)
1002 let v:errmsg = 'fugitive: '.error
1003 return 'echoerr v:errmsg'
1005 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1009 let one = s:repo().translate(':1:'.path)
1010 let two = s:repo().translate(':2:'.path)
1011 let three = s:repo().translate(':3:'.path)
1012 for nr in range(1,bufnr('$'))
1013 if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three)
1014 execute nr.'bdelete'
1019 let zero = s:repo().translate(':0:'.path)
1020 for tab in range(1,tabpagenr('$'))
1021 for winnr in range(1,tabpagewinnr(tab,'$'))
1022 let bufnr = tabpagebuflist(tab)[winnr-1]
1023 let bufname = bufname(bufnr)
1024 if bufname ==# zero && bufnr != mybufnr
1025 execute 'tabnext '.tab
1027 execute winnr.'wincmd w'
1028 let restorewinnr = 1
1031 let lnum = line('.')
1032 let last = line('$')
1033 silent $read `=file`
1034 silent execute '1,'.last.'delete_'
1039 if exists('restorewinnr')
1042 execute 'tabnext '.mytab
1048 call fugitive#reload_status()
1052 function! s:Wq(force,...) abort
1053 let bang = a:force ? '!' : ''
1054 if exists('b:fugitive_commit_arguments')
1057 let result = call(s:function('s:Write'),[a:force]+a:000)
1058 if result =~# '^\%(write\|wq\|echoerr\)'
1059 return s:sub(result,'^write','wq')
1061 return result.'|quit'.bang
1068 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
1069 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
1070 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
1072 augroup fugitive_diff
1074 autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diff_off_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif
1075 autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | diffoff | endif
1078 function! s:diff_window_count()
1080 for nr in range(1,winnr('$'))
1081 let c += getwinvar(nr,'&diff')
1086 function! s:diff_off_all(dir)
1087 for nr in range(1,winnr('$'))
1088 if getwinvar(nr,'&diff')
1090 execute nr.'wincmd w'
1091 let restorewinnr = 1
1093 if exists('b:git_dir') && b:git_dir ==# a:dir
1096 if exists('restorewinnr')
1103 function! s:buffer_compare_age(commit) dict abort
1104 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1105 let my_score = get(scores,':'.self.commit(),0)
1106 let their_score = get(scores,':'.a:commit,0)
1107 if my_score || their_score
1108 return my_score < their_score ? -1 : my_score != their_score
1109 elseif self.commit() ==# a:commit
1112 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1113 if base ==# self.commit()
1115 elseif base ==# a:commit
1118 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1119 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1120 return my_time < their_time ? -1 : my_time != their_time
1123 call s:add_methods('buffer',['compare_age'])
1125 function! s:Diff(bang,...) abort
1126 let split = a:bang ? 'split' : 'vsplit'
1127 if exists(':DiffGitCached')
1128 return 'DiffGitCached'
1129 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1131 execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1132 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1135 execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1136 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1145 let file = s:buffer().path('/')
1147 let file = s:buffer().path(':0:')
1148 elseif a:1 =~# '^:/.'
1150 let file = s:repo().rev_parse(a:1).s:buffer().path(':')
1152 return 'echoerr v:errmsg'
1155 let file = s:buffer().expand(a:1)
1157 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1158 let file = file.s:buffer().path(':')
1161 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1164 let spec = s:repo().translate(file)
1165 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1166 if s:buffer().compare_age(commit) < 0
1167 execute 'rightbelow '.split.' `=spec`'
1169 execute 'leftabove '.split.' `=spec`'
1176 return 'echoerr v:errmsg'
1181 " Gmove, Gremove {{{1
1183 function! s:Move(force,destination)
1184 if a:destination =~# '^/'
1185 let destination = a:destination[1:-1]
1187 let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
1188 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1189 let destination = destination[strlen(s:repo().tree('')):-1]
1192 if isdirectory(s:buffer().name())
1193 " Work around Vim parser idiosyncrasy
1194 let discarded = s:buffer().setvar('&swapfile',0)
1196 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1198 let v:errmsg = 'fugitive: '.message
1199 return 'echoerr v:errmsg'
1201 let destination = s:repo().tree(destination)
1202 if isdirectory(destination)
1203 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1205 call fugitive#reload_status()
1206 if s:buffer().commit() == ''
1207 if isdirectory(destination)
1208 return 'edit '.s:fnameescape(destination)
1210 return 'saveas! '.s:fnameescape(destination)
1213 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
1217 function! s:MoveComplete(A,L,P)
1219 return s:repo().superglob(a:A)
1221 let matches = split(glob(a:A.'*'),"\n")
1222 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1227 function! s:Remove(force)
1228 if s:buffer().commit() ==# ''
1230 elseif s:buffer().commit() ==# '0'
1231 let cmd = ['rm','--cached']
1233 let v:errmsg = 'fugitive: rm not supported here'
1234 return 'echoerr v:errmsg'
1237 let cmd += ['--force']
1239 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1241 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1242 return 'echoerr '.string(v:errmsg)
1244 call fugitive#reload_status()
1245 return 'bdelete'.(a:force ? '!' : '')
1249 augroup fugitive_remove
1251 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1252 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1253 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1260 augroup fugitive_blame
1262 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1263 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1264 autocmd Syntax fugitiveblame call s:BlameSyntax()
1265 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
1268 function! s:Blame(bang,line1,line2,count,args) abort
1270 if s:buffer().path() == ''
1271 call s:throw('file or blob required')
1273 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != []
1274 call s:throw('unsupported option')
1276 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1277 let git_dir = s:repo().dir()
1278 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1279 if s:buffer().commit() =~# '\D\|..'
1280 let cmd += [s:buffer().commit()]
1282 let cmd += ['--contents', '-']
1284 let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo())
1286 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1289 execute cd.'`=s:repo().tree()`'
1292 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1294 let error = tempname()
1295 let temp = error.'.fugitiveblame'
1297 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1299 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1306 call s:throw(join(readfile(error),"\n"))
1308 let bufnr = bufnr('')
1309 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1311 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1314 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1317 windo set noscrollbind
1318 exe winnr.'wincmd w'
1319 setlocal scrollbind nowrap nofoldenable
1320 let top = line('w0') + &scrolloff
1321 let current = line('.')
1322 exe 'leftabove vsplit '.temp
1323 let b:git_dir = git_dir
1324 let b:fugitive_type = 'blame'
1325 let b:fugitive_blamed_bufnr = bufnr
1326 let w:fugitive_restore = restore
1327 let b:fugitive_blame_arguments = join(a:args,' ')
1328 call s:Detect(expand('%:p'))
1332 execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1)
1333 setlocal nomodified nomodifiable bufhidden=delete nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
1334 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1335 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
1336 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1337 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1338 nnoremap <buffer> <silent> o :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+'))<CR>
1339 nnoremap <buffer> <silent> O :<C-U>exe <SID>Edit("tabedit", matchstr(getline('.'),'\x\+'))<CR>
1349 return 'echoerr v:errmsg'
1353 function! s:BlameJump(suffix) abort
1354 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1355 if commit =~# '^0\+$'
1358 let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]')
1359 let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1361 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1363 let args = b:fugitive_blame_arguments
1364 let offset = line('.') - line('w0')
1365 let bufnr = bufnr('%')
1366 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1368 exe winnr.'wincmd w'
1370 execute s:Edit('edit',commit.a:suffix.':'.path)
1374 execute 'Gblame '.args
1376 let delta = line('.') - line('w0') - offset
1378 execute 'norm! 'delta."\<C-E>"
1380 execute 'norm! '(-delta)."\<C-Y>"
1386 function! s:BlameSyntax() abort
1387 let b:current_syntax = 'fugitiveblame'
1388 syn match FugitiveblameBoundary "^\^"
1389 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1390 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1391 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1392 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1393 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1394 syn match FugitiveblameLineNumber " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation
1395 syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite
1396 syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite
1397 syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite
1398 syn match FugitiveblameShort "\d\+)" contained contains=FugitiveblameLineNumber
1399 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1400 hi def link FugitiveblameBoundary Keyword
1401 hi def link FugitiveblameHash Identifier
1402 hi def link FugitiveblameUncommitted Function
1403 hi def link FugitiveblameTime PreProc
1404 hi def link FugitiveblameLineNumber Number
1405 hi def link FugitiveblameOriginalFile String
1406 hi def link FugitiveblameOriginalLineNumber Float
1407 hi def link FugitiveblameShort FugitiveblameDelimiter
1408 hi def link FugitiveblameDelimiter Delimiter
1409 hi def link FugitiveblameNotCommittedYet Comment
1415 call s:command("-bar -bang -count=0 -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1417 function! s:Browse(bang,line1,count,...) abort
1419 let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1421 let expanded = s:buffer().rev()
1423 let expanded = s:buffer().path('/')
1425 let expanded = s:buffer().expand(rev)
1427 let full = s:repo().translate(expanded)
1429 if full =~# '^fugitive://'
1430 let commit = matchstr(full,'://.*//\zs\w\+')
1431 let path = matchstr(full,'://.*//\w\+\zs/.*')
1433 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1437 let path = path[1:-1]
1438 elseif s:repo().bare()
1439 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1442 let path = full[strlen(s:repo().tree())+1:-1]
1443 if path =~# '^\.git/'
1445 elseif isdirectory(full)
1451 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1452 let body = readfile(s:repo().dir(path[5:-1]))[0]
1453 if body =~# '^\x\{40\}$'
1457 elseif body =~# '^ref: refs/'
1458 let path = '.git/' . matchstr(body,'ref: \zs.*')
1462 if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1463 let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1464 elseif path =~# '^\.git/refs/remotes/.'
1465 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1467 let remote = 'origin'
1468 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1469 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1470 let branch = s:sub(path,'^\.git/refs/\w+/','')
1472 if filereadable(s:repo().dir('refs/remotes/'.branch))
1473 let remote = matchstr(branch,'[^/]\+')
1474 let rev = rev[strlen(remote)+1:-1]
1477 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1480 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1482 let remote = 'origin'
1483 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1484 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1490 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1495 let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1497 let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count ? a:line1 : 0)
1501 call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1506 return 'echomsg '.string(url)
1508 return 'echomsg '.string(url).'|silent Git web--browse '.shellescape(url,1)
1511 return 'echoerr v:errmsg'
1515 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1517 let repo_path = matchstr(a:url,'^\%(https\=://\|git://\|git@\)github\.com[/:]\zs.\{-\}\ze\%(\.git\)\=$')
1521 let root = 'https://github.com/' . repo_path
1522 if path =~# '^\.git/refs/heads/'
1523 let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1525 return root . '/commits/' . path[16:-1]
1527 return root . '/commits/' . branch
1529 elseif path =~# '^\.git/refs/.'
1530 return root . '/commits/' . matchstr(path,'[^/]\+$')
1531 elseif path =~# '.git/\%(config$\|hooks\>\)'
1532 return root . '/admin'
1533 elseif path =~# '^\.git\>'
1536 if a:rev =~# '^[[:alnum:]._-]\+:'
1537 let commit = matchstr(a:rev,'^[^:]*')
1538 elseif a:commit =~# '^\d\=$'
1539 let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
1540 let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
1545 let commit = a:commit
1548 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
1549 elseif a:type == 'blob'
1550 let url = root . '/blob/' . commit . '/' . path
1551 if a:line2 && a:line1 == a:line2
1552 let url .= '#L' . a:line1
1554 let url .= '#L' . a:line1 . '-' . a:line2
1556 elseif a:type == 'tag'
1557 let commit = matchstr(getline(3),'^tag \zs.*')
1558 let url = root . '/tree/' . commit
1560 let url = root . '/commit/' . commit
1565 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
1566 let output = a:repo.git_chomp('instaweb','-b','unknown')
1567 if output =~# 'http://'
1568 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
1572 if a:path =~# '^\.git/refs/.'
1573 return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
1574 elseif a:path =~# '^\.git\>'
1578 if a:commit =~# '^\x\{40\}$'
1579 if a:type ==# 'commit'
1580 let url .= ';a=commit'
1582 let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
1584 if a:type ==# 'blob'
1585 let tmp = tempname()
1586 silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
1587 let url .= ';h=' . readfile(tmp)[0]
1590 let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
1592 call s:throw('fugitive: cannot browse uncommitted file')
1595 let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
1598 let url .= ';f=' . a:path
1601 let url .= '#l' . a:1
1609 function! s:ReplaceCmd(cmd,...) abort
1610 let fn = bufname('')
1611 let tmp = tempname()
1617 let old_index = $GIT_INDEX_FILE
1618 let $GIT_INDEX_FILE = a:1
1620 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
1624 silent exe '!'.escape(prefix.a:cmd,'%#').' > '.tmp
1627 if exists('old_index')
1628 let $GIT_INDEX_FILE = old_index
1631 silent exe 'keepalt file '.tmp
1633 silent exe 'keepalt file '.s:fnameescape(fn)
1635 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1638 function! s:BufReadIndex()
1639 if !exists('b:fugitive_display_format')
1640 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1642 let b:fugitive_display_format = b:fugitive_display_format % 2
1643 let b:fugitive_type = 'index'
1645 let b:git_dir = s:repo().dir()
1647 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
1650 let index = expand('%:p')
1652 if b:fugitive_display_format
1653 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
1656 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1659 execute cd.'`=s:repo().tree()`'
1660 call s:ReplaceCmd(s:repo().git_command('status'),index)
1666 setlocal ro noma nomod nomodeline bufhidden=delete
1667 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
1668 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
1669 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff()<CR>
1670 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff()<CR>
1671 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1672 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1673 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff()<CR>
1674 nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
1675 xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
1676 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1677 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1678 nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR>
1679 nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR>
1683 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
1685 return 'echoerr v:errmsg'
1689 function! s:FileRead()
1691 let repo = s:repo(s:ExtractGitDir(expand('<amatch>')))
1692 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
1693 let hash = repo.rev_parse(path)
1697 let type = repo.git_chomp('cat-file','-t',hash)
1699 " TODO: use count, if possible
1700 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
1702 return 'echoerr v:errmsg'
1706 function! s:BufReadIndexFile()
1708 let b:fugitive_type = 'blob'
1709 let b:git_dir = s:repo().dir()
1710 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
1712 catch /^fugitive: rev-parse/
1713 silent exe 'doau BufNewFile '.s:fnameescape(bufname(''))
1716 return 'echoerr v:errmsg'
1720 function! s:BufWriteIndexFile()
1721 let tmp = tempname()
1723 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
1724 let stage = matchstr(expand('<amatch>'),'//\zs\d')
1725 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
1726 let sha1 = readfile(tmp)[0]
1727 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
1729 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
1731 let info = old_mode.' '.sha1.' '.stage."\t".path
1732 call writefile([info],tmp)
1734 let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info'))
1736 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
1738 if v:shell_error == 0
1740 silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
1741 call fugitive#reload_status()
1744 return 'echoerr '.string('fugitive: '.error)
1751 function! s:BufReadObject()
1754 let b:git_dir = s:repo().dir()
1755 let hash = s:buffer().sha1()
1756 if !exists("b:fugitive_type")
1757 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
1759 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1760 return "echoerr 'fugitive: unrecognized git type'"
1762 let firstline = getline('.')
1763 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1764 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1767 let pos = getpos('.')
1771 if b:fugitive_type == 'tree'
1772 let b:fugitive_display_format = b:fugitive_display_format % 2
1773 if b:fugitive_display_format
1774 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
1776 call s:ReplaceCmd(s:repo().git_command('show',hash))
1778 elseif b:fugitive_type == 'tag'
1779 let b:fugitive_display_format = b:fugitive_display_format % 2
1780 if b:fugitive_display_format
1781 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1783 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
1785 elseif b:fugitive_type == 'commit'
1786 let b:fugitive_display_format = b:fugitive_display_format % 2
1787 if b:fugitive_display_format
1788 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1790 call s:ReplaceCmd(s:repo().git_command('show','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
1791 call search('^parent ')
1792 if getline('.') ==# 'parent '
1795 silent s/\%(^parent\)\@<! /\rparent /ge
1797 if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1802 elseif b:fugitive_type ==# 'blob'
1803 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1805 call setpos('.',pos)
1806 setlocal ro noma nomod nomodeline
1807 if b:fugitive_type !=# 'blob'
1809 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
1810 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
1817 return 'echoerr v:errmsg'
1821 augroup fugitive_files
1823 autocmd BufReadCmd *.git/index exe s:BufReadIndex()
1824 autocmd BufReadCmd *.git/*index*.lock exe s:BufReadIndex()
1825 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
1826 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
1827 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
1828 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
1829 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
1830 autocmd FileType git call s:JumpInit()
1836 function! s:JumpInit() abort
1837 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
1839 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
1840 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
1841 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
1842 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
1843 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1844 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1845 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',<SID>buffer().containing_commit())<CR>
1846 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',<SID>buffer().containing_commit())<CR>
1847 nnoremap <buffer> <silent> cp :<C-U>exe <SID>Edit('pedit',<SID>buffer().containing_commit())<CR>
1851 function! s:GF(mode) abort
1853 let buffer = s:buffer()
1854 let myhash = buffer.sha1()
1856 if buffer.type('tree')
1857 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1858 if showtree && line('.') == 1
1860 elseif showtree && line('.') > 2
1861 return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
1862 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
1863 return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
1866 elseif buffer.type('blob')
1867 let ref = expand("<cfile>")
1869 let sha1 = buffer.repo().rev_parse(ref)
1873 return s:Edit(a:mode,ref)
1879 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
1880 let ref = matchstr(getline('.'),'\x\{40\}')
1881 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
1882 return s:Edit(a:mode,file)
1884 elseif getline('.') =~# '^#\trenamed:.* -> '
1885 let file = '/'.matchstr(getline('.'),' -> \zs.*')
1886 return s:Edit(a:mode,file)
1887 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
1888 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( (new commits)\)\=$')
1889 return s:Edit(a:mode,file)
1890 elseif getline('.') =~# '^#\t.'
1891 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
1892 return s:Edit(a:mode,file)
1893 elseif getline('.') =~# ': needs merge$'
1894 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
1895 return s:Edit(a:mode,file).'|Gdiff'
1897 elseif getline('.') ==# '# Not currently on any branch.'
1898 return s:Edit(a:mode,'HEAD')
1899 elseif getline('.') =~# '^# On branch '
1900 let file = 'refs/heads/'.getline('.')[12:]
1901 return s:Edit(a:mode,file)
1902 elseif getline('.') =~# "^# Your branch .*'"
1903 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
1904 return s:Edit(a:mode,file)
1907 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1909 if getline('.') =~# '^ref: '
1910 let ref = strpart(getline('.'),5)
1912 elseif getline('.') =~# '^parent \x\{40\}\>'
1913 let ref = matchstr(getline('.'),'\x\{40\}')
1914 let line = line('.')
1916 while getline(line) =~# '^parent '
1920 return s:Edit(a:mode,ref)
1922 elseif getline('.') =~ '^tree \x\{40\}$'
1923 let ref = matchstr(getline('.'),'\x\{40\}')
1924 if s:repo().rev_parse(myhash.':') == ref
1925 let ref = myhash.':'
1927 return s:Edit(a:mode,ref)
1929 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
1930 let ref = matchstr(getline('.'),'\x\{40\}')
1931 let type = matchstr(getline(line('.')+1),'type \zs.*')
1933 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
1936 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
1937 let ref = matchstr(getline('.'),'\x\{40\}')
1938 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
1940 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
1941 let ref = getline('.')[4:]
1943 elseif getline('.') =~# '^rename from '
1944 let ref = 'a/'.getline('.')[12:]
1945 elseif getline('.') =~# '^rename to '
1946 let ref = 'b/'.getline('.')[10:]
1948 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1949 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1950 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1953 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1954 let line = getline(line('.')-1)
1955 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1956 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1959 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
1960 let ref = getline('.')
1966 let ref = s:sub(ref,'^a/','HEAD:')
1967 let ref = s:sub(ref,'^b/',':0:')
1969 let dref = s:sub(dref,'^a/','HEAD:')
1972 let ref = s:sub(ref,'^a/',myhash.'^:')
1973 let ref = s:sub(ref,'^b/',myhash.':')
1975 let dref = s:sub(dref,'^a/',myhash.'^:')
1979 if ref ==# '/dev/null'
1981 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
1985 return s:Edit(a:mode,ref) . '|'.dcmd.' '.s:fnameescape(dref)
1987 return s:Edit(a:mode,ref)
1993 return 'echoerr v:errmsg'
2000 function! s:repo_head_ref() dict abort
2001 return readfile(s:repo().dir('HEAD'))[0]
2004 call s:add_methods('repo',['head_ref'])
2006 function! fugitive#statusline(...)
2007 if !exists('b:git_dir')
2011 if s:buffer().commit() != ''
2012 let status .= ':' . s:buffer().commit()[0:7]
2014 let head = s:repo().head_ref()
2015 if head =~# '^ref: '
2016 let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')'
2017 elseif head =~# '^\x\{40\}$'
2018 let status .= '('.head[0:7].')'
2020 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2021 return ',GIT'.status
2023 return '[Git'.status.']'
2027 function! s:repo_config(conf) dict abort
2028 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
2031 function! s:repo_user() dict abort
2032 let username = s:repo().config('user.name')
2033 let useremail = s:repo().config('user.email')
2034 return username.' <'.useremail.'>'
2037 call s:add_methods('repo',['config', 'user'])
2041 " vim:set ft=vim ts=8 sw=2 sts=2: