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
9 if exists('g:loaded_fugitive') || &cp
12 let g:loaded_fugitive = 1
14 if !exists('g:fugitive_git_executable')
15 let g:fugitive_git_executable = 'git'
20 function! s:function(name) abort
21 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
24 function! s:sub(str,pat,rep) abort
25 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
28 function! s:gsub(str,pat,rep) abort
29 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
32 function! s:shellesc(arg) abort
33 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
35 elseif &shell =~# 'cmd' && a:arg !~# '"'
38 return shellescape(a:arg)
42 function! s:fnameescape(file) abort
43 if exists('*fnameescape')
44 return fnameescape(a:file)
46 return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
50 function! s:throw(string) abort
51 let v:errmsg = 'fugitive: '.a:string
59 let v:warningmsg = a:str
62 function! s:shellslash(path)
63 if exists('+shellslash') && !&shellslash
64 return s:gsub(a:path,'\\','/')
70 function! s:add_methods(namespace, method_names) abort
71 for name in a:method_names
72 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
77 function! s:command(definition) abort
78 let s:commands += [a:definition]
81 function! s:define_commands()
82 for command in s:commands
83 exe 'command! -buffer '.command
87 function! s:compatibility_check()
88 if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning')
89 let g:fugitive_did_compatibility_warning = 1
90 call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim")
94 augroup fugitive_utility
96 autocmd User Fugitive call s:define_commands()
97 autocmd VimEnter * call s:compatibility_check()
100 let s:abstract_prototype = {}
103 " Initialization {{{1
105 function! s:ExtractGitDir(path) abort
106 let path = s:shellslash(a:path)
107 if path =~? '^fugitive://.*//'
108 return matchstr(path,'fugitive://\zs.\{-\}\ze//')
110 let fn = fnamemodify(path,':s?[\/]$??')
114 if filereadable(fn . '/.git/HEAD')
115 return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','')
116 elseif fn =~ '\.git$' && filereadable(fn . '/HEAD')
117 return s:sub(simplify(fnamemodify(fn,':p')),'\W$','')
120 let fn = fnamemodify(ofn,':h')
125 function! s:Detect(path)
126 if exists('b:git_dir') && b:git_dir ==# ''
129 if !exists('b:git_dir')
130 let dir = s:ExtractGitDir(a:path)
135 if exists('b:git_dir')
136 silent doautocmd User Fugitive
137 cnoremap <expr> <buffer> <C-R><C-G> fugitive#buffer().rev()
138 let buffer = fugitive#buffer()
139 if expand('%:p') =~# '//'
140 call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)',''))
142 if b:git_dir !~# ',' && stridx(buffer.getvar('&tags'),b:git_dir.'/tags') == -1
144 call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/'.&filetype.'.tags')
146 call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/tags')
153 autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
154 autocmd FileType netrw call s:Detect(expand('<afile>:p'))
155 autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
156 autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_restore')
162 let s:repo_prototype = {}
165 function! s:repo(...) abort
166 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p')))
168 if has_key(s:repos,dir)
169 let repo = get(s:repos,dir)
171 let repo = {'git_dir': dir}
172 let s:repos[dir] = repo
174 return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep')
176 call s:throw('not a git repository: '.expand('%:p'))
179 function! s:repo_dir(...) dict abort
180 return join([self.git_dir]+a:000,'/')
183 function! s:repo_tree(...) dict abort
185 let dir = fnamemodify(self.git_dir,':h')
186 return join([dir]+a:000,'/')
188 call s:throw('no work tree')
191 function! s:repo_bare() dict abort
192 return self.dir() !~# '/\.git$'
195 function! s:repo_translate(spec) dict abort
196 if a:spec ==# '.' || a:spec ==# '/.'
197 return self.bare() ? self.dir() : self.tree()
198 elseif a:spec =~# '^/'
199 return fnamemodify(self.dir(),':h').a:spec
200 elseif a:spec =~# '^:[0-3]:'
201 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
202 elseif a:spec ==# ':'
203 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE)
204 return fnamemodify($GIT_INDEX_FILE,':p')
206 return self.dir('index')
208 elseif a:spec =~# '^:/'
209 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
210 return 'fugitive://'.self.dir().'//'.ref
211 elseif a:spec =~# '^:'
212 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
213 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
214 return self.dir(a:spec)
215 elseif filereadable(s:repo().dir('refs/'.a:spec))
216 return self.dir('refs/'.a:spec)
217 elseif filereadable(s:repo().dir('refs/tags/'.a:spec))
218 return self.dir('refs/tags/'.a:spec)
219 elseif filereadable(s:repo().dir('refs/heads/'.a:spec))
220 return self.dir('refs/heads/'.a:spec)
221 elseif filereadable(s:repo().dir('refs/remotes/'.a:spec))
222 return self.dir('refs/remotes/'.a:spec)
223 elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD'))
224 return self.dir('refs/remotes/'.a:spec,'/HEAD')
227 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
228 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
229 return 'fugitive://'.self.dir().'//'.ref.path
231 return self.tree(a:spec)
236 call s:add_methods('repo',['dir','tree','bare','translate'])
238 function! s:repo_git_command(...) dict abort
239 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
240 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
243 function! s:repo_git_chomp(...) dict abort
244 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
247 function! s:repo_git_chomp_in_tree(...) dict abort
248 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
251 execute cd.'`=s:repo().tree()`'
252 return call(s:repo().git_chomp, a:000, s:repo())
258 function! s:repo_rev_parse(rev) dict abort
259 let hash = self.git_chomp('rev-parse','--verify',a:rev)
260 if hash =~ '\<\x\{40\}$'
261 return matchstr(hash,'\<\x\{40\}$')
263 call s:throw('rev-parse '.a:rev.': '.hash)
266 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
268 function! s:repo_dirglob(base) dict abort
269 let base = s:sub(a:base,'^/','')
270 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
271 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
275 function! s:repo_superglob(base) dict abort
276 if a:base =~# '^/' || a:base !~# ':'
279 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
280 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
281 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
285 let base = s:sub(a:base,'^/','')
286 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
287 call map(matches,'s:shellslash(v:val)')
288 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
289 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
290 let results += matches
294 elseif a:base =~# '^:'
295 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
296 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
297 if a:base !~# '^:[0-3]\%(:\|$\)'
298 call filter(entries,'v:val[1] == "0"')
299 call map(entries,'v:val[2:-1]')
301 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
305 let tree = matchstr(a:base,'.*[:/]')
306 let entries = split(self.git_chomp('ls-tree',tree),"\n")
307 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
308 call map(entries,'tree.s:sub(v:val,".*\t","")')
309 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
313 call s:add_methods('repo',['dirglob','superglob'])
315 function! s:repo_keywordprg() dict abort
316 let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show'
317 if has('gui_running') && !has('win32')
318 return g:fugitive_git_executable . ' --no-pager' . args
320 return g:fugitive_git_executable . args
324 call s:add_methods('repo',['keywordprg'])
329 let s:buffer_prototype = {}
331 function! s:buffer(...) abort
332 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
333 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
334 if buffer.getvar('git_dir') !=# ''
337 call s:throw('not a git repository: '.expand('%:p'))
340 function! fugitive#buffer(...) abort
341 return s:buffer(a:0 ? a:1 : '%')
344 function! s:buffer_getvar(var) dict abort
345 return getbufvar(self['#'],a:var)
348 function! s:buffer_setvar(var,value) dict abort
349 return setbufvar(self['#'],a:var,a:value)
352 function! s:buffer_getline(lnum) dict abort
353 return getbufline(self['#'],a:lnum)[0]
356 function! s:buffer_repo() dict abort
357 return s:repo(self.getvar('git_dir'))
360 function! s:buffer_type(...) dict abort
361 if self.getvar('fugitive_type') != ''
362 let type = self.getvar('fugitive_type')
363 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
365 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
367 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
369 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
371 elseif isdirectory(self.spec())
372 let type = 'directory'
373 elseif self.spec() == ''
375 elseif filereadable(self.spec())
381 return !empty(filter(copy(a:000),'v:val ==# type'))
389 function! s:buffer_spec() dict abort
390 let bufname = bufname(self['#'])
392 for i in split(bufname,'[^:]\zs\\')
393 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
395 return s:shellslash(fnamemodify(retval,':p'))
400 function! s:buffer_spec() dict abort
401 let bufname = bufname(self['#'])
402 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
407 function! s:buffer_name() dict abort
411 function! s:buffer_commit() dict abort
412 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
415 function! s:buffer_path(...) dict abort
416 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
418 let rev = s:sub(rev,'\w*','')
420 let rev = self.spec()[strlen(self.repo().tree()) : -1]
422 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
425 function! s:buffer_rev() dict abort
426 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
428 return ':'.rev[0].':'.rev[2:-1]
430 return s:sub(rev,'/',':')
431 elseif self.spec() =~ '\.git/index$'
433 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
434 return self.spec()[strlen(self.repo().dir())+1 : -1]
440 function! s:buffer_sha1() dict abort
441 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
442 return self.repo().rev_parse(self.rev())
448 function! s:buffer_expand(rev) dict abort
449 if a:rev =~# '^:[0-3]$'
450 let file = a:rev.self.path(':')
451 elseif a:rev =~# '^[-:]/$'
452 let file = '/'.self.path()
453 elseif a:rev =~# '^-'
454 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
455 elseif a:rev =~# '^@{'
456 let file = 'HEAD'.a:rev.self.path(':')
457 elseif a:rev =~# '^[~^]'
458 let commit = s:sub(self.commit(),'^\d=$','HEAD')
459 let file = commit.a:rev.self.path(':')
463 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
466 function! s:buffer_containing_commit() dict abort
467 if self.commit() =~# '^\d$'
469 elseif self.commit() =~# '.'
476 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit'])
481 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
483 function! s:ExecuteInTree(cmd) abort
484 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
487 execute cd.'`=s:repo().tree()`'
494 function! s:Git(bang,cmd) abort
495 let git = s:repo().git_command()
496 if has('gui_running') && !has('win32')
497 let git .= ' --no-pager'
499 let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
500 call s:ExecuteInTree('!'.git.' '.cmd)
501 call fugitive#reload_status()
502 return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
505 function! s:GitComplete(A,L,P) abort
506 if !exists('s:exec_path')
507 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
509 let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
510 if a:L =~ ' [[:alnum:]-]\+ '
511 return s:repo().superglob(a:A)
515 return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
522 function! s:DirComplete(A,L,P) abort
523 let matches = s:repo().dirglob(a:A)
527 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>)`")
528 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>)`")
533 call s:command("-bar Gstatus :execute s:Status()")
535 function! s:Status() abort
539 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
541 return 'echoerr v:errmsg'
546 function! fugitive#reload_status() abort
547 let mytab = tabpagenr()
548 for tab in [mytab] + range(1,tabpagenr('$'))
549 for winnr in range(1,tabpagewinnr(tab,'$'))
550 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
551 execute 'tabnext '.tab
553 execute winnr.'wincmd w'
558 call s:BufReadIndex()
561 if exists('restorewinnr')
564 execute 'tabnext '.mytab
571 function! s:StageDiff(...) abort
572 let cmd = a:0 ? a:1 : 'Gdiff'
573 let section = getline(search('^# .*:$','bnW'))
574 let line = getline('.')
575 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
576 if filename ==# '' && section == '# Changes to be committed:'
577 return 'Git diff --cached'
578 elseif filename ==# ''
580 elseif line =~# '^#\trenamed:' && filename =~ ' -> '
581 let [old, new] = split(filename,' -> ')
582 execute 'Gedit '.s:fnameescape(':0:'.new)
583 return cmd.' HEAD:'.s:fnameescape(old)
584 elseif section == '# Changes to be committed:'
585 execute 'Gedit '.s:fnameescape(':0:'.filename)
588 execute 'Gedit '.s:fnameescape('/'.filename)
593 function! s:StageToggle(lnum1,lnum2) abort
596 for lnum in range(a:lnum1,a:lnum2)
597 let line = getline(lnum)
599 if line ==# '# Changes to be committed:'
600 call repo.git_chomp_in_tree('reset','-q')
603 if !search('^# Untracked files:$','W')
604 call search('^# Change','W')
607 elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
608 call repo.git_chomp_in_tree('add','-u')
611 if !search('^# Untracked files:$','W')
612 call search('^# Change','W')
615 elseif line ==# '# Untracked files:'
616 " Work around Vim parser idiosyncrasy
617 call repo.git_chomp_in_tree('add','-N','.')
620 if !search('^# Change\%(d but not updated\|s not staged for commit\):$','W')
621 call search('^# Change','W')
625 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (\a\+ [[:alpha:], ]\+)\)\=$')
629 if !exists('first_filename')
630 let first_filename = filename
633 let section = getline(search('^# .*:$','bnW'))
634 if line =~# '^#\trenamed:' && filename =~ ' -> '
635 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
636 let filename = cmd[-1]
637 elseif section =~? ' to be '
638 let cmd = ['reset','-q','--',filename]
639 elseif line =~# '^#\tdeleted:'
640 let cmd = ['rm','--',filename]
642 let cmd = ['add','--',filename]
644 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
646 if exists('first_filename')
647 let jump = first_filename
648 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
649 if f !=# '' | let jump = f | endif
650 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
651 if f !=# '' | let jump = f | endif
655 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( (new commits)\)\=\$','W')
657 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
659 return 'echoerr v:errmsg'
664 function! s:StagePatch(lnum1,lnum2) abort
668 for lnum in range(a:lnum1,a:lnum2)
669 let line = getline(lnum)
670 if line ==# '# Changes to be committed:'
671 return 'Git reset --patch'
672 elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
673 return 'Git add --patch'
675 let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
679 if !exists('first_filename')
680 let first_filename = filename
683 let section = getline(search('^# .*:$','bnW'))
684 if line =~# '^#\trenamed:' && filename =~ ' -> '
685 let reset += [split(filename,' -> ')[1]]
686 elseif section =~? ' to be '
687 let reset += [filename]
688 elseif line !~# '^#\tdeleted:'
689 let add += [filename]
694 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
697 execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
699 if exists('first_filename')
703 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( (new commits)\)\=\$','W')
706 return 'echoerr v:errmsg'
714 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
716 function! s:Commit(args) abort
717 let old_type = s:buffer().type()
718 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
720 let msgfile = s:repo().dir('COMMIT_EDITMSG')
721 let outfile = tempname()
722 let errorfile = tempname()
724 execute cd.'`=s:repo().tree()`'
727 let old_editor = $GIT_EDITOR
728 let $GIT_EDITOR = 'false'
730 let command = 'env GIT_EDITOR=false '
732 let command .= s:repo().git_command('commit').' '.a:args
734 silent execute '!('.command.' > '.outfile.') >& '.errorfile
735 elseif a:args =~# '\%(^\| \)--interactive\>'
736 execute '!'.command.' 2> '.errorfile
738 silent execute '!'.command.' > '.outfile.' 2> '.errorfile
741 if filereadable(outfile)
742 for line in readfile(outfile)
748 let errors = readfile(errorfile)
749 let error = get(errors,-2,get(errors,-1,'!'))
750 if error =~# '\<false''\=\.$'
752 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
753 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
754 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
755 let args = '-F '.s:shellesc(msgfile).' '.args
756 if args !~# '\%(^\| \)--cleanup\>'
757 let args = '--cleanup=strip '.args
759 let old_nr = bufnr('')
760 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
763 keepalt split `=msgfile`
765 if old_type ==# 'index'
766 execute 'bdelete '.old_nr
768 let b:fugitive_commit_arguments = args
769 setlocal bufhidden=delete filetype=gitcommit
778 return 'echoerr v:errmsg'
780 if exists('old_editor')
781 let $GIT_EDITOR = old_editor
784 call delete(errorfile)
786 call fugitive#reload_status()
790 function! s:CommitComplete(A,L,P) abort
791 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
792 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']
793 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
795 return s:repo().superglob(a:A)
799 function! s:FinishCommit()
800 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
802 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
803 return s:Commit(args)
808 augroup fugitive_commit
810 autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
816 if !exists('g:fugitive_summary_format')
817 let g:fugitive_summary_format = '%s'
820 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)")
821 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
823 function! s:Grep(bang,arg) abort
824 let grepprg = &grepprg
825 let grepformat = &grepformat
826 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
829 execute cd.'`=s:repo().tree()`'
830 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
831 let &grepformat = '%f:%l:%m'
832 exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
833 let list = getqflist()
835 if bufname(entry.bufnr) =~ ':'
836 let entry.filename = s:repo().translate(bufname(entry.bufnr))
838 elseif a:arg =~# '\%(^\| \)--cached\>'
839 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
843 call setqflist(list,'r')
844 if !a:bang && !empty(list)
845 return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
847 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
850 let &grepprg = grepprg
851 let &grepformat = grepformat
856 function! s:Log(cmd,...)
857 let path = s:buffer().path('/')
858 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
861 let cmd = ['--no-pager', 'log', '--no-color']
862 let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')]
863 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
864 if s:buffer().commit() =~# '\x\{40\}'
865 let cmd += [s:buffer().commit()]
866 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
867 let cmd += [s:buffer().path()[5:-1]]
870 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
872 let cmd += ['--',path[1:-1]]
874 let grepformat = &grepformat
875 let grepprg = &grepprg
876 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
879 execute cd.'`=s:repo().tree()`'
880 let &grepprg = call(s:repo().git_command,cmd,s:repo())
881 let &grepformat = '%f::%m'
884 let &grepformat = grepformat
885 let &grepprg = grepprg
891 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
893 function! s:Edit(cmd,...) abort
897 let file = s:buffer().expand(a:1)
898 elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>'
899 let file = s:buffer().path(':')
901 let file = s:buffer().path('/')
904 let file = s:repo().translate(file)
906 return 'echoerr v:errmsg'
909 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
911 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
914 return a:cmd.' '.s:fnameescape(file)
918 function! s:EditComplete(A,L,P) abort
919 return s:repo().superglob(a:A)
922 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',<f-args>)")
923 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',<f-args>)")
924 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit :execute s:Edit('pedit<bang>',<f-args>)")
925 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit :execute s:Edit('split<bang>',<f-args>)")
926 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit :execute s:Edit('vsplit<bang>',<f-args>)")
927 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit<bang>',<f-args>)")
928 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read<bang>',<f-args>)")
933 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
934 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
935 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
937 function! s:Write(force,...) abort
938 if exists('b:fugitive_commit_arguments')
939 return 'write|bdelete'
940 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
942 elseif s:buffer().type() == 'index'
945 let mytab = tabpagenr()
946 let mybufnr = bufnr('')
947 let path = a:0 ? a:1 : s:buffer().path()
949 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
951 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
952 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) !=# ''
953 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
954 return 'echoerr v:errmsg'
956 let file = s:repo().translate(path)
958 for nr in range(1,bufnr('$'))
959 if fnamemodify(bufname(nr),':p') ==# file
964 if treebufnr > 0 && treebufnr != bufnr('')
965 let temp = tempname()
966 silent execute '%write '.temp
967 for tab in [mytab] + range(1,tabpagenr('$'))
968 for winnr in range(1,tabpagewinnr(tab,'$'))
969 if tabpagebuflist(tab)[winnr-1] == treebufnr
970 execute 'tabnext '.tab
972 execute winnr.'wincmd w'
978 silent execute '$read '.temp
979 silent execute '1,'.last.'delete_'
984 if exists('restorewinnr')
987 execute 'tabnext '.mytab
993 call writefile(readfile(temp,'b'),file,'b')
996 execute 'write! '.s:fnameescape(s:repo().translate(path))
1000 let error = s:repo().git_chomp_in_tree('add', '--force', file)
1002 let error = s:repo().git_chomp_in_tree('add', file)
1005 let v:errmsg = 'fugitive: '.error
1006 return 'echoerr v:errmsg'
1008 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1012 let one = s:repo().translate(':1:'.path)
1013 let two = s:repo().translate(':2:'.path)
1014 let three = s:repo().translate(':3:'.path)
1015 for nr in range(1,bufnr('$'))
1016 if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three)
1017 execute nr.'bdelete'
1022 let zero = s:repo().translate(':0:'.path)
1023 for tab in range(1,tabpagenr('$'))
1024 for winnr in range(1,tabpagewinnr(tab,'$'))
1025 let bufnr = tabpagebuflist(tab)[winnr-1]
1026 let bufname = bufname(bufnr)
1027 if bufname ==# zero && bufnr != mybufnr
1028 execute 'tabnext '.tab
1030 execute winnr.'wincmd w'
1031 let restorewinnr = 1
1034 let lnum = line('.')
1035 let last = line('$')
1036 silent $read `=file`
1037 silent execute '1,'.last.'delete_'
1042 if exists('restorewinnr')
1045 execute 'tabnext '.mytab
1051 call fugitive#reload_status()
1055 function! s:Wq(force,...) abort
1056 let bang = a:force ? '!' : ''
1057 if exists('b:fugitive_commit_arguments')
1060 let result = call(s:function('s:Write'),[a:force]+a:000)
1061 if result =~# '^\%(write\|wq\|echoerr\)'
1062 return s:sub(result,'^write','wq')
1064 return result.'|quit'.bang
1071 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
1072 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
1073 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
1075 augroup fugitive_diff
1077 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
1078 autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | diffoff | endif
1081 function! s:diff_window_count()
1083 for nr in range(1,winnr('$'))
1084 let c += getwinvar(nr,'&diff')
1089 function! s:diff_off_all(dir)
1090 for nr in range(1,winnr('$'))
1091 if getwinvar(nr,'&diff')
1093 execute nr.'wincmd w'
1094 let restorewinnr = 1
1096 if exists('b:git_dir') && b:git_dir ==# a:dir
1099 if exists('restorewinnr')
1106 function! s:buffer_compare_age(commit) dict abort
1107 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1108 let my_score = get(scores,':'.self.commit(),0)
1109 let their_score = get(scores,':'.a:commit,0)
1110 if my_score || their_score
1111 return my_score < their_score ? -1 : my_score != their_score
1112 elseif self.commit() ==# a:commit
1115 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1116 if base ==# self.commit()
1118 elseif base ==# a:commit
1121 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1122 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1123 return my_time < their_time ? -1 : my_time != their_time
1126 call s:add_methods('buffer',['compare_age'])
1128 function! s:Diff(bang,...) abort
1129 let split = a:bang ? 'split' : 'vsplit'
1130 if exists(':DiffGitCached')
1131 return 'DiffGitCached'
1132 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1134 execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1135 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1138 execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1139 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1148 let file = s:buffer().path('/')
1150 let file = s:buffer().path(':0:')
1151 elseif a:1 =~# '^:/.'
1153 let file = s:repo().rev_parse(a:1).s:buffer().path(':')
1155 return 'echoerr v:errmsg'
1158 let file = s:buffer().expand(a:1)
1160 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1161 let file = file.s:buffer().path(':')
1164 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1167 let spec = s:repo().translate(file)
1168 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1169 if s:buffer().compare_age(commit) < 0
1170 execute 'rightbelow '.split.' `=spec`'
1172 execute 'leftabove '.split.' `=spec`'
1179 return 'echoerr v:errmsg'
1184 " Gmove, Gremove {{{1
1186 function! s:Move(force,destination)
1187 if a:destination =~# '^/'
1188 let destination = a:destination[1:-1]
1190 let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
1191 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1192 let destination = destination[strlen(s:repo().tree('')):-1]
1195 if isdirectory(s:buffer().name())
1196 " Work around Vim parser idiosyncrasy
1197 let discarded = s:buffer().setvar('&swapfile',0)
1199 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1201 let v:errmsg = 'fugitive: '.message
1202 return 'echoerr v:errmsg'
1204 let destination = s:repo().tree(destination)
1205 if isdirectory(destination)
1206 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1208 call fugitive#reload_status()
1209 if s:buffer().commit() == ''
1210 if isdirectory(destination)
1211 return 'edit '.s:fnameescape(destination)
1213 return 'saveas! '.s:fnameescape(destination)
1216 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
1220 function! s:MoveComplete(A,L,P)
1222 return s:repo().superglob(a:A)
1224 let matches = split(glob(a:A.'*'),"\n")
1225 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1230 function! s:Remove(force)
1231 if s:buffer().commit() ==# ''
1233 elseif s:buffer().commit() ==# '0'
1234 let cmd = ['rm','--cached']
1236 let v:errmsg = 'fugitive: rm not supported here'
1237 return 'echoerr v:errmsg'
1240 let cmd += ['--force']
1242 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1244 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1245 return 'echoerr '.string(v:errmsg)
1247 call fugitive#reload_status()
1248 return 'bdelete'.(a:force ? '!' : '')
1252 augroup fugitive_remove
1254 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1255 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1256 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1263 augroup fugitive_blame
1265 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1266 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1267 autocmd Syntax fugitiveblame call s:BlameSyntax()
1268 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
1271 function! s:Blame(bang,line1,line2,count,args) abort
1273 if s:buffer().path() == ''
1274 call s:throw('file or blob required')
1276 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != []
1277 call s:throw('unsupported option')
1279 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1280 let git_dir = s:repo().dir()
1281 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1282 if s:buffer().commit() =~# '\D\|..'
1283 let cmd += [s:buffer().commit()]
1285 let cmd += ['--contents', '-']
1287 let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo())
1289 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1292 execute cd.'`=s:repo().tree()`'
1295 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1297 let error = tempname()
1298 let temp = error.'.fugitiveblame'
1300 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1302 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1309 call s:throw(join(readfile(error),"\n"))
1311 let bufnr = bufnr('')
1312 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1314 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1317 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1320 windo set noscrollbind
1321 exe winnr.'wincmd w'
1322 setlocal scrollbind nowrap nofoldenable
1323 let top = line('w0') + &scrolloff
1324 let current = line('.')
1325 exe 'leftabove vsplit '.temp
1326 let b:git_dir = git_dir
1327 let b:fugitive_type = 'blame'
1328 let b:fugitive_blamed_bufnr = bufnr
1329 let w:fugitive_restore = restore
1330 let b:fugitive_blame_arguments = join(a:args,' ')
1331 call s:Detect(expand('%:p'))
1335 execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1)
1336 setlocal nomodified nomodifiable bufhidden=delete nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
1337 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1338 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
1339 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1340 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1341 nnoremap <buffer> <silent> o :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+'))<CR>
1342 nnoremap <buffer> <silent> O :<C-U>exe <SID>Edit("tabedit", matchstr(getline('.'),'\x\+'))<CR>
1352 return 'echoerr v:errmsg'
1356 function! s:BlameJump(suffix) abort
1357 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1358 if commit =~# '^0\+$'
1361 let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]')
1362 let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1364 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1366 let args = b:fugitive_blame_arguments
1367 let offset = line('.') - line('w0')
1368 let bufnr = bufnr('%')
1369 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1371 exe winnr.'wincmd w'
1373 execute s:Edit('edit',commit.a:suffix.':'.path)
1377 execute 'Gblame '.args
1379 let delta = line('.') - line('w0') - offset
1381 execute 'norm! 'delta."\<C-E>"
1383 execute 'norm! '(-delta)."\<C-Y>"
1389 function! s:BlameSyntax() abort
1390 let b:current_syntax = 'fugitiveblame'
1391 syn match FugitiveblameBoundary "^\^"
1392 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1393 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1394 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1395 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1396 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1397 syn match FugitiveblameLineNumber " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation
1398 syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite
1399 syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite
1400 syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite
1401 syn match FugitiveblameShort "\d\+)" contained contains=FugitiveblameLineNumber
1402 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1403 hi def link FugitiveblameBoundary Keyword
1404 hi def link FugitiveblameHash Identifier
1405 hi def link FugitiveblameUncommitted Function
1406 hi def link FugitiveblameTime PreProc
1407 hi def link FugitiveblameLineNumber Number
1408 hi def link FugitiveblameOriginalFile String
1409 hi def link FugitiveblameOriginalLineNumber Float
1410 hi def link FugitiveblameShort FugitiveblameDelimiter
1411 hi def link FugitiveblameDelimiter Delimiter
1412 hi def link FugitiveblameNotCommittedYet Comment
1418 call s:command("-bar -bang -count=0 -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1420 function! s:Browse(bang,line1,count,...) abort
1422 let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1424 let expanded = s:buffer().rev()
1426 let expanded = s:buffer().path('/')
1428 let expanded = s:buffer().expand(rev)
1430 let full = s:repo().translate(expanded)
1432 if full =~# '^fugitive://'
1433 let commit = matchstr(full,'://.*//\zs\w\+')
1434 let path = matchstr(full,'://.*//\w\+\zs/.*')
1436 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1440 let path = path[1:-1]
1441 elseif s:repo().bare()
1442 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1445 let path = full[strlen(s:repo().tree())+1:-1]
1446 if path =~# '^\.git/'
1448 elseif isdirectory(full)
1454 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1455 let body = readfile(s:repo().dir(path[5:-1]))[0]
1456 if body =~# '^\x\{40\}$'
1460 elseif body =~# '^ref: refs/'
1461 let path = '.git/' . matchstr(body,'ref: \zs.*')
1465 if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1466 let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1467 elseif path =~# '^\.git/refs/remotes/.'
1468 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1470 let remote = 'origin'
1471 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1472 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1473 let branch = s:sub(path,'^\.git/refs/\w+/','')
1475 if filereadable(s:repo().dir('refs/remotes/'.branch))
1476 let remote = matchstr(branch,'[^/]\+')
1477 let rev = rev[strlen(remote)+1:-1]
1480 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1483 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1485 let remote = 'origin'
1486 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1487 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1493 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1498 let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1500 let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count ? a:line1 : 0)
1504 call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1509 return 'echomsg '.string(url)
1511 return 'echomsg '.string(url).'|silent Git web--browse '.shellescape(url,1)
1514 return 'echoerr v:errmsg'
1518 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1520 let repo_path = matchstr(a:url,'^\%(https\=://\|git://\|git@\)github\.com[/:]\zs.\{-\}\ze\%(\.git\)\=$')
1524 let root = 'https://github.com/' . repo_path
1525 if path =~# '^\.git/refs/heads/'
1526 let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1528 return root . '/commits/' . path[16:-1]
1530 return root . '/commits/' . branch
1532 elseif path =~# '^\.git/refs/.'
1533 return root . '/commits/' . matchstr(path,'[^/]\+$')
1534 elseif path =~# '.git/\%(config$\|hooks\>\)'
1535 return root . '/admin'
1536 elseif path =~# '^\.git\>'
1539 if a:rev =~# '^[[:alnum:]._-]\+:'
1540 let commit = matchstr(a:rev,'^[^:]*')
1541 elseif a:commit =~# '^\d\=$'
1542 let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
1543 let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
1548 let commit = a:commit
1551 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
1552 elseif a:type == 'blob'
1553 let url = root . '/blob/' . commit . '/' . path
1554 if a:line2 && a:line1 == a:line2
1555 let url .= '#L' . a:line1
1557 let url .= '#L' . a:line1 . '-' . a:line2
1559 elseif a:type == 'tag'
1560 let commit = matchstr(getline(3),'^tag \zs.*')
1561 let url = root . '/tree/' . commit
1563 let url = root . '/commit/' . commit
1568 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
1569 let output = a:repo.git_chomp('instaweb','-b','unknown')
1570 if output =~# 'http://'
1571 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
1575 if a:path =~# '^\.git/refs/.'
1576 return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
1577 elseif a:path =~# '^\.git\>'
1581 if a:commit =~# '^\x\{40\}$'
1582 if a:type ==# 'commit'
1583 let url .= ';a=commit'
1585 let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
1587 if a:type ==# 'blob'
1588 let tmp = tempname()
1589 silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
1590 let url .= ';h=' . readfile(tmp)[0]
1593 let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
1595 call s:throw('fugitive: cannot browse uncommitted file')
1598 let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
1601 let url .= ';f=' . a:path
1604 let url .= '#l' . a:1
1612 function! s:ReplaceCmd(cmd,...) abort
1613 let fn = bufname('')
1614 let tmp = tempname()
1620 let old_index = $GIT_INDEX_FILE
1621 let $GIT_INDEX_FILE = a:1
1623 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
1627 silent exe '!'.escape(prefix.a:cmd,'%#').' > '.tmp
1630 if exists('old_index')
1631 let $GIT_INDEX_FILE = old_index
1634 silent exe 'keepalt file '.tmp
1636 silent exe 'keepalt file '.s:fnameescape(fn)
1638 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1641 function! s:BufReadIndex()
1642 if !exists('b:fugitive_display_format')
1643 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1645 let b:fugitive_display_format = b:fugitive_display_format % 2
1646 let b:fugitive_type = 'index'
1648 let b:git_dir = s:repo().dir()
1650 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
1653 let index = expand('%:p')
1655 if b:fugitive_display_format
1656 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
1659 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1662 execute cd.'`=s:repo().tree()`'
1663 call s:ReplaceCmd(s:repo().git_command('status'),index)
1669 setlocal ro noma nomod nomodeline bufhidden=delete
1670 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
1671 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
1672 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff()<CR>
1673 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff()<CR>
1674 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1675 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1676 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff()<CR>
1677 nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
1678 xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
1679 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1680 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1681 nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR>
1682 nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR>
1686 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
1688 return 'echoerr v:errmsg'
1692 function! s:FileRead()
1694 let repo = s:repo(s:ExtractGitDir(expand('<amatch>')))
1695 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
1696 let hash = repo.rev_parse(path)
1700 let type = repo.git_chomp('cat-file','-t',hash)
1702 " TODO: use count, if possible
1703 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
1705 return 'echoerr v:errmsg'
1709 function! s:BufReadIndexFile()
1711 let b:fugitive_type = 'blob'
1712 let b:git_dir = s:repo().dir()
1713 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
1715 catch /^fugitive: rev-parse/
1716 silent exe 'doau BufNewFile '.s:fnameescape(bufname(''))
1719 return 'echoerr v:errmsg'
1723 function! s:BufWriteIndexFile()
1724 let tmp = tempname()
1726 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
1727 let stage = matchstr(expand('<amatch>'),'//\zs\d')
1728 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
1729 let sha1 = readfile(tmp)[0]
1730 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
1732 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
1734 let info = old_mode.' '.sha1.' '.stage."\t".path
1735 call writefile([info],tmp)
1737 let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info'))
1739 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
1741 if v:shell_error == 0
1743 silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
1744 call fugitive#reload_status()
1747 return 'echoerr '.string('fugitive: '.error)
1754 function! s:BufReadObject()
1757 let b:git_dir = s:repo().dir()
1758 let hash = s:buffer().sha1()
1759 if !exists("b:fugitive_type")
1760 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
1762 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1763 return "echoerr 'fugitive: unrecognized git type'"
1765 let firstline = getline('.')
1766 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1767 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1770 let pos = getpos('.')
1774 if b:fugitive_type == 'tree'
1775 let b:fugitive_display_format = b:fugitive_display_format % 2
1776 if b:fugitive_display_format
1777 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
1779 call s:ReplaceCmd(s:repo().git_command('show',hash))
1781 elseif b:fugitive_type == 'tag'
1782 let b:fugitive_display_format = b:fugitive_display_format % 2
1783 if b:fugitive_display_format
1784 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1786 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
1788 elseif b:fugitive_type == 'commit'
1789 let b:fugitive_display_format = b:fugitive_display_format % 2
1790 if b:fugitive_display_format
1791 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1793 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))
1794 call search('^parent ')
1795 if getline('.') ==# 'parent '
1798 silent s/\%(^parent\)\@<! /\rparent /ge
1800 if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1805 elseif b:fugitive_type ==# 'blob'
1806 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1808 call setpos('.',pos)
1809 setlocal ro noma nomod nomodeline
1810 if b:fugitive_type !=# 'blob'
1812 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
1813 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
1820 return 'echoerr v:errmsg'
1824 augroup fugitive_files
1826 autocmd BufReadCmd *.git/index exe s:BufReadIndex()
1827 autocmd BufReadCmd *.git/*index*.lock exe s:BufReadIndex()
1828 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
1829 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
1830 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
1831 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
1832 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
1833 autocmd FileType git call s:JumpInit()
1839 function! s:JumpInit() abort
1840 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
1842 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
1843 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
1844 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
1845 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
1846 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1847 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1848 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',<SID>buffer().containing_commit())<CR>
1849 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',<SID>buffer().containing_commit())<CR>
1850 nnoremap <buffer> <silent> cp :<C-U>exe <SID>Edit('pedit',<SID>buffer().containing_commit())<CR>
1854 function! s:GF(mode) abort
1856 let buffer = s:buffer()
1857 let myhash = buffer.sha1()
1859 if buffer.type('tree')
1860 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1861 if showtree && line('.') == 1
1863 elseif showtree && line('.') > 2
1864 return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
1865 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
1866 return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
1869 elseif buffer.type('blob')
1870 let ref = expand("<cfile>")
1872 let sha1 = buffer.repo().rev_parse(ref)
1876 return s:Edit(a:mode,ref)
1882 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
1883 let ref = matchstr(getline('.'),'\x\{40\}')
1884 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
1885 return s:Edit(a:mode,file)
1887 elseif getline('.') =~# '^#\trenamed:.* -> '
1888 let file = '/'.matchstr(getline('.'),' -> \zs.*')
1889 return s:Edit(a:mode,file)
1890 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
1891 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( (new commits)\)\=$')
1892 return s:Edit(a:mode,file)
1893 elseif getline('.') =~# '^#\t.'
1894 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
1895 return s:Edit(a:mode,file)
1896 elseif getline('.') =~# ': needs merge$'
1897 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
1898 return s:Edit(a:mode,file).'|Gdiff'
1900 elseif getline('.') ==# '# Not currently on any branch.'
1901 return s:Edit(a:mode,'HEAD')
1902 elseif getline('.') =~# '^# On branch '
1903 let file = 'refs/heads/'.getline('.')[12:]
1904 return s:Edit(a:mode,file)
1905 elseif getline('.') =~# "^# Your branch .*'"
1906 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
1907 return s:Edit(a:mode,file)
1910 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1912 if getline('.') =~# '^ref: '
1913 let ref = strpart(getline('.'),5)
1915 elseif getline('.') =~# '^parent \x\{40\}\>'
1916 let ref = matchstr(getline('.'),'\x\{40\}')
1917 let line = line('.')
1919 while getline(line) =~# '^parent '
1923 return s:Edit(a:mode,ref)
1925 elseif getline('.') =~ '^tree \x\{40\}$'
1926 let ref = matchstr(getline('.'),'\x\{40\}')
1927 if s:repo().rev_parse(myhash.':') == ref
1928 let ref = myhash.':'
1930 return s:Edit(a:mode,ref)
1932 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
1933 let ref = matchstr(getline('.'),'\x\{40\}')
1934 let type = matchstr(getline(line('.')+1),'type \zs.*')
1936 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
1939 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
1940 let ref = matchstr(getline('.'),'\x\{40\}')
1941 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
1943 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
1944 let ref = getline('.')[4:]
1946 elseif getline('.') =~# '^rename from '
1947 let ref = 'a/'.getline('.')[12:]
1948 elseif getline('.') =~# '^rename to '
1949 let ref = 'b/'.getline('.')[10:]
1951 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1952 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1953 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1956 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1957 let line = getline(line('.')-1)
1958 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1959 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1962 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
1963 let ref = getline('.')
1969 let ref = s:sub(ref,'^a/','HEAD:')
1970 let ref = s:sub(ref,'^b/',':0:')
1972 let dref = s:sub(dref,'^a/','HEAD:')
1975 let ref = s:sub(ref,'^a/',myhash.'^:')
1976 let ref = s:sub(ref,'^b/',myhash.':')
1978 let dref = s:sub(dref,'^a/',myhash.'^:')
1982 if ref ==# '/dev/null'
1984 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
1988 return s:Edit(a:mode,ref) . '|'.dcmd.' '.s:fnameescape(dref)
1990 return s:Edit(a:mode,ref)
1996 return 'echoerr v:errmsg'
2003 function! s:repo_head_ref() dict abort
2004 return readfile(s:repo().dir('HEAD'))[0]
2007 call s:add_methods('repo',['head_ref'])
2009 function! fugitive#statusline(...)
2010 if !exists('b:git_dir')
2014 if s:buffer().commit() != ''
2015 let status .= ':' . s:buffer().commit()[0:7]
2017 let head = s:repo().head_ref()
2018 if head =~# '^ref: '
2019 let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')'
2020 elseif head =~# '^\x\{40\}$'
2021 let status .= '('.head[0:7].')'
2023 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2024 return ',GIT'.status
2026 return '[Git'.status.']'
2030 function! s:repo_config(conf) dict abort
2031 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
2034 function! s:repo_user() dict abort
2035 let username = s:repo().config('user.name')
2036 let useremail = s:repo().config('user.email')
2037 return username.' <'.useremail.'>'
2040 call s:add_methods('repo',['config', 'user'])
2044 " vim:set ft=vim ts=8 sw=2 sts=2: