" fugitive.vim - Fugitive " Maintainer: Tim Pope if (exists("g:loaded_fugitive") && g:loaded_fugitive) || &cp finish endif let g:loaded_fugitive = 1 if !exists('g:fugitive_git_executable') let g:fugitive_git_executable = 'git' endif " Utility {{{1 function! s:function(name) abort return function(substitute(a:name,'^s:',matchstr(expand(''), '\d\+_'),'')) endfunction function! s:sub(str,pat,rep) abort return substitute(a:str,'\v\C'.a:pat,a:rep,'') endfunction function! s:gsub(str,pat,rep) abort return substitute(a:str,'\v\C'.a:pat,a:rep,'g') endfunction function! s:shellesc(arg) abort if a:arg =~ '^[A-Za-z0-9_/.-]\+$' return a:arg else return shellescape(a:arg) endif endfunction function! s:fnameescape(file) abort if exists('*fnameescape') return fnameescape(a:file) else return escape(a:file," \t\n*?[{`$\\%#'\"|!<") endif endfunction function! s:throw(string) abort let v:errmsg = 'fugitive: '.a:string throw v:errmsg endfunction function! s:add_methods(namespace, method_names) abort for name in a:method_names let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name) endfor endfunction let s:commands = [] function! s:command(definition) abort let s:commands += [a:definition] endfunction function! s:define_commands() for command in s:commands exe 'command! -buffer '.command endfor endfunction augroup fugitive_commands autocmd! autocmd User Fugitive call s:define_commands() augroup END let s:abstract_prototype = {} " }}}1 " Initialization {{{1 function! s:ExtractGitDir(path) abort if a:path =~? '^fugitive://.*//' return matchstr(a:path,'fugitive://\zs.\{-\}\ze//') endif let fn = fnamemodify(a:path,':s?[\/]$??') let ofn = "" let nfn = fn while fn != ofn if isdirectory(fn . '/.git') return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'/$','') elseif fn =~ '\.git$' && filereadable(fn . '/HEAD') return s:sub(simplify(fnamemodify(fn,':p')),'/$','') endif let ofn = fn let fn = fnamemodify(ofn,':h') endwhile return '' endfunction function! s:Detect() if !exists('b:git_dir') let dir = s:ExtractGitDir(expand('%:p')) if dir != '' let b:git_dir = dir endif endif if exists('b:git_dir') silent doautocmd User Fugitive cnoremap fugitive#buffer().rev() endif endfunction augroup fugitive autocmd! autocmd BufNewFile,BufReadPost * call s:Detect() autocmd FileType netrw call s:Detect() autocmd BufWinLeave * execute getbufvar(+expand(''), 'fugitive_restore') augroup END " }}}1 " Repository {{{1 let s:repo_prototype = {} let s:repos = {} function! s:repo(...) abort let dir = a:0 ? a:1 : (exists('b:git_dir') ? b:git_dir : s:ExtractGitDir(expand('%:p'))) if dir == '' call s:throw('not a git repository: '.expand('%:p')) else if has_key(s:repos,dir) let repo = get(s:repos,dir) else let repo = {'git_dir': dir} let s:repos[dir] = repo endif return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep') endif endfunction function! s:repo_dir(...) dict abort return join([self.git_dir]+a:000,'/') endfunction function! s:repo_tree(...) dict abort if self.bare() call s:throw('no work tree') else let dir = fnamemodify(self.git_dir,':h') return join([dir]+a:000,'/') endif endfunction function! s:repo_bare() dict abort return self.dir() !~# '/\.git$' endfunction function! s:repo_translate(spec) dict abort if a:spec ==# '.' || a:spec ==# '/.' return self.bare() ? self.dir() : self.tree() elseif a:spec =~# '^/' return fnamemodify(self.dir(),':h').a:spec elseif a:spec =~# '^:[0-3]:' return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1] elseif a:spec ==# ':' return self.dir('index') elseif a:spec =~# '^:/' let ref = self.rev_parse(matchstr(a:spec,'.[^:]*')) return 'fugitive://'.self.dir().'//'.ref elseif a:spec =~# '^:' return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1] elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec)) return self.dir(a:spec) elseif filereadable(s:repo().dir('refs/'.a:spec)) return self.dir('refs/'.a:spec) elseif filereadable(s:repo().dir('refs/tags/'.a:spec)) return self.dir('refs/tags/'.a:spec) elseif filereadable(s:repo().dir('refs/heads/'.a:spec)) return self.dir('refs/heads/'.a:spec) elseif filereadable(s:repo().dir('refs/remotes/'.a:spec)) return self.dir('refs/remotes/'.a:spec) elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD')) return self.dir('refs/remotes/'.a:spec,'/HEAD') else try let ref = self.rev_parse(matchstr(a:spec,'[^:]*')) let path = s:sub(matchstr(a:spec,':.*'),'^:','/') return 'fugitive://'.self.dir().'//'.ref.path catch /^fugitive:/ return self.tree(a:spec) endtry endif endfunction call s:add_methods('repo',['dir','tree','bare','translate']) function! s:repo_git_command(...) dict abort let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir) return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'') endfunction function! s:repo_git_chomp(...) dict abort return s:sub(system(call(self.git_command,a:000,self)),'\n$','') endfunction function! s:repo_git_chomp_in_tree(...) dict abort let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let dir = getcwd() try execute cd.' `=s:repo().tree()`' return call(s:repo().git_chomp, a:000, s:repo()) finally execute cd.' `=dir`' endtry endfunction function! s:repo_rev_parse(rev) dict abort let hash = self.git_chomp('rev-parse','--verify',a:rev) if hash =~ '^\x\{40\}$' return hash else call s:throw('rev-parse '.a:rev.': '.hash) endif endfunction call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse']) function! s:repo_dirglob(base) dict abort let base = s:sub(a:base,'^/','') let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n") call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]') return matches endfunction function! s:repo_superglob(base) dict abort if a:base =~# '^/' || a:base !~# ':' let results = [] if a:base !~# '^/' let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"] let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n")) call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') let results += heads endif if !self.bare() let base = s:sub(a:base,'^/','') let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n") call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val') call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]') let results += matches endif return results elseif a:base =~# '^:' let entries = split(self.git_chomp('ls-files','--stage'),"\n") call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")') if a:base !~# '^:[0-3]\%(:\|$\)' call filter(entries,'v:val[1] == "0"') call map(entries,'v:val[2:-1]') endif call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') return entries else let tree = matchstr(a:base,'.*[:/]') let entries = split(self.git_chomp('ls-tree',tree),"\n") call map(entries,'s:sub(v:val,"^04.*\\zs$","/")') call map(entries,'tree.s:sub(v:val,".*\t","")') return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') endif endfunction call s:add_methods('repo',['dirglob','superglob']) function! s:repo_keywordprg() dict abort let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show' if has("gui_running") return g:fugitive_git_executable . ' --no-pager' . args else return g:fugitive_git_executable . args endif endfunction call s:add_methods('repo',['keywordprg']) " }}}1 " Buffer {{{1 let s:buffer_prototype = {} function! s:buffer(...) abort let buffer = {'#': bufnr(a:0 ? a:1 : '%')} call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep') if buffer.getvar('git_dir') == '' call s:throw('not a git repository: '.expand('%:p')) endif return buffer endfunction function! fugitive#buffer(...) abort return s:buffer(a:0 ? a:1 : '%') endfunction function! s:buffer_getvar(var) dict abort return getbufvar(self['#'],a:var) endfunction function! s:buffer_getline(lnum) dict abort return getbufline(self['#'],a:lnum)[0] endfunction function! s:buffer_repo() dict abort return s:repo(self.getvar('git_dir')) endfunction function! s:buffer_type(...) dict abort if self.getvar('fugitive_type') != '' let type = self.getvar('fugitive_type') elseif fnamemodify(self.name(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$' let type = 'head' elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == '' let type = 'tree' elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t' let type = 'tree' elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t' let type = 'index' elseif isdirectory(self.name()) let type = 'directory' elseif filereadable(self.name()) let type = 'file' else let type = '' endif if a:0 return !empty(filter(copy(a:000),'v:val ==# type')) else return type endif endfunction function! s:buffer_name() dict abort return fnamemodify(bufname(self['#']),':p') endfunction function! s:buffer_commit() dict abort return matchstr(self.name(),'^fugitive://.\{-\}//\zs\w*') endfunction function! s:buffer_path(...) dict abort let rev = matchstr(self.name(),'^fugitive://.\{-\}//\zs.*') if rev != '' let rev = s:sub(rev,'\w*','') elseif self.name() =~ '\.git/refs/\|\.git/.*HEAD$' let rev = '' else let rev = self.name()[strlen(self.repo().tree()) : -1] endif return s:sub(rev,'^/',a:0 ? a:1 : '') endfunction function! s:buffer_rev() dict abort let rev = matchstr(self.name(),'^fugitive://.\{-\}//\zs.*') if rev =~ '^\x/' return ':'.rev[0].':'.rev[2:-1] elseif rev =~ '.' return s:sub(rev,'/',':') elseif self.name() =~ '\.git/index$' return ':' elseif self.name() =~ '\.git/refs/\|\.git/.*HEAD$' return self.name()[strlen(self.repo().dir())+1 : -1] else return self.path() endif endfunction function! s:buffer_sha1() dict abort if self.name() =~ '^fugitive://' || self.name() =~ '\.git/refs/\|\.git/.*HEAD$' return self.repo().rev_parse(self.rev()) else return '' endif endfunction function! s:buffer_expand(rev) dict abort if a:rev =~# '^:[0-3]$' let file = a:rev.self.path(':') elseif a:rev =~# '^-' let file = 'HEAD^{}'.a:rev[1:-1].self.path(':') elseif a:rev =~# '^@{' let file = 'HEAD'.a:rev.self.path(':') elseif a:rev =~# '^[~^]' let commit = s:sub(self.commit(),'^\d=$','HEAD') let file = commit.a:rev.self.path(':') else let file = a:rev endif return s:sub(file,'\%$',self.path()) endfunction function! s:buffer_containing_commit() dict abort if self.commit() =~# '\x\{40\}' return self.commit() elseif self.commit() =~# '.' return ':' else return 'HEAD' endif endfunction call s:add_methods('buffer',['getvar','getline','repo','type','name','commit','path','rev','sha1','expand','containing_commit']) " }}}1 " Git {{{1 call s:command("-bar -bang -nargs=? -complete=customlist,s:GitComplete Git :call s:Git(0,)") function! s:ExecuteInTree(cmd) abort let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let dir = getcwd() try execute cd.' `=s:repo().tree()`' execute a:cmd finally execute cd.' `=dir`' endtry endfunction function! s:Git(bang,cmd) abort let git = s:repo().git_command() if has('gui_running') let git .= ' --no-pager' endif call s:ExecuteInTree('!'.git.' '.a:cmd) endfunction function! s:GitComplete(A,L,P) abort if !exists('s:exec_path') let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','') endif let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'v:val[strlen(s:exec_path)+5 : -1]') if a:L =~ ' [[:alnum:]-]\+ ' return s:repo().superglob(a:A) elseif a:A == '' return cmds else return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A') endif endfunction " }}}1 " Gcd, Glcd {{{1 function! s:DirComplete(A,L,P) abort let matches = s:repo().dirglob(a:A) return matches endfunction call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :cd `=s:repo().bare() ? s:repo().dir() : s:repo().tree()`") call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :lcd `=s:repo().bare() ? s:repo().dir() : s:repo().tree()`") " }}}1 " Ggrep, Glog {{{1 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(0,)") call s:command("-bar -bang Glog :execute s:Log('grep')") function! s:Grep(bang,arg) abort let grepprg = &grepprg let grepformat = &grepformat let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let cd .= s:fnameescape(getcwd()) try cd `=s:repo().tree()` let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n') let &grepformat = '%f:%l:%m' exe 'grep! '.a:arg let list = getqflist() for entry in list if bufname(entry.bufnr) =~ ':' let entry.filename = s:repo().translate(bufname(entry.bufnr)) unlet! entry.bufnr elseif a:arg =~# '\%(^\| \)--cached\>' let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr)) unlet! entry.bufnr endif endfor call setqflist(list,'r') if !a:bang && !empty(list) return 'cfirst' else return '' endif finally let &grepprg = grepprg let &grepformat = grepformat exe cd endtry endfunction function! s:Log(cmd) let cmd = ['--no-pager', 'log', '--no-color', '--pretty=format:fugitive://'.s:repo().dir().'//%H'.s:buffer().path('/').'::%s'] if s:buffer().commit() =~# '\x\{40\}' let cmd += [s:buffer().commit().'^'] endif let cmd += ['--'] if s:buffer().path() != '' let cmd += [s:buffer().path()] endif let grepformat = &grepformat let grepprg = &grepprg try let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%') let &grepformat = '%f::%m' exe a:cmd finally let &grepformat = grepformat let &grepprg = grepprg endtry endfunction " }}}1 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1 function! s:Edit(cmd,...) abort if a:0 && a:1 == '' return '' elseif a:0 let file = s:buffer().expand(a:1) elseif s:buffer().commit() ==# '' let file = s:buffer().path(':') else let file = s:buffer().path('/') endif let file = s:repo().translate(file) if a:cmd =~# 'read!$' return '%delete|read '.s:fnameescape(file).'|1delete_|diffupdate|'.line('.') else return a:cmd.' '.s:fnameescape(file) endif endfunction function! s:EditComplete(A,L,P) abort return s:repo().superglob(a:A) endfunction call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit',)") call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit',)") call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit :execute s:Edit('pedit',)") call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit :execute s:Edit('split',)") call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit :execute s:Edit('vsplit',)") call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit',)") call s:command("-bar -bang -nargs=? -range -complete=customlist,s:EditComplete Gread :execute s:Edit(',read',)") " }}}1 " Gwrite {{{1 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(0,)") function! s:Write(force,...) abort let mytab = tabpagenr() let mybufnr = bufnr('') let path = a:0 ? a:1 : s:buffer().path() if path =~# '^:\d\>' return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path))) endif let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$') 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) !=# '' let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)' return 'echoerr v:errmsg' endif let file = s:repo().translate(path) let treebufnr = 0 for nr in range(1,bufnr('$')) if fnamemodify(bufname(nr),':p') == file let treebufnr = nr endif endfor if treebufnr > 0 && treebufnr != bufnr('') let temp = tempname() silent execute '%write '.temp for tab in [mytab] + range(1,tabpagenr('$')) for winnr in range(1,tabpagewinnr(tab,'$')) if tabpagebuflist(tab)[winnr-1] == treebufnr execute 'tabnext '.tab execute winnr.'wincmd w' try let lnum = line('.') let last = line('$') silent execute '$read '.temp silent execute '1,'.last.'delete_' silent write! silent execute lnum let did = 1 finally wincmd p execute 'tabnext '.tab endtry endif endfor endfor if !exists('did') call writefile(readfile(temp,'b'),file,'b') endif else execute 'write! '.s:fnameescape(s:repo().translate(path)) endif let error = s:repo().git_chomp_in_tree('add', file) if v:shell_error let v:errmsg = 'fugitive: '.error return 'echoerr v:errmsg' endif if s:buffer().path() == path && s:buffer().commit() =~# '^\d$' set nomodified endif let one = s:repo().translate(':1:'.path) let two = s:repo().translate(':2:'.path) let three = s:repo().translate(':3:'.path) for nr in range(1,bufnr('$')) if !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three) execute nr.'bdelete' endif endfor let zero = s:repo().translate(':0:'.path) for tab in range(1,tabpagenr('$')) for winnr in range(1,tabpagewinnr(tab,'$')) let bufnr = tabpagebuflist(tab)[winnr-1] let bufname = bufname(bufnr) if bufname ==# zero && bufnr != mybufnr execute 'tabnext '.tab execute winnr.'wincmd w' try let lnum = line('.') let last = line('$') silent $read `=file` silent execute '1,'.last.'delete_' silent execute lnum set nomodified diffupdate finally wincmd p execute 'tabnext '.mytab endtry break endif endfor endfor return 'checktime' endfunction " }}}1 " Gdiff {{{1 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff()") augroup fugitive_diff autocmd BufWinLeave * if winnr('$') == 2 && &diff && getbufvar(+expand(''), 'git_dir') !=# '' | diffoff! | endif autocmd BufWinEnter * if winnr('$') == 1 && &diff && getbufvar(+expand(''), 'git_dir') !=# '' | diffoff | endif augroup END function! s:Diff(...) abort if exists(':DiffGitCached') return 'DiffGitCached' elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# '' leftabove vsplit `=fugitive#buffer().repo().translate(s:buffer().expand(':2'))` diffthis wincmd p rightbelow vsplit `=fugitive#buffer().repo().translate(s:buffer().expand(':3'))` diffthis wincmd p diffthis return '' elseif a:0 if a:1 ==# '' return '' elseif a:1 ==# '/' let file = s:buffer().path('/') elseif a:1 ==# ':' let file = s:buffer().path(':0:') else let file = s:buffer().expand(a:1) endif if file !~ ':' && file !~ '^/' let file = file.s:buffer().path(':') endif else let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/') endif try vsplit `=fugitive#buffer().repo().translate(file)` diffthis wincmd p diffthis return '' catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction " }}}1 " Gmove, Gremove {{{1 function! s:Move(force,destination) if a:destination =~# '^/' let destination = a:destination[1:-1] else let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p') if destination[0:strlen(s:repo().tree())] == s:repo().tree('') let destination = destination[strlen(s:repo().tree('')):-1] endif endif let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo()) if v:shell_error let v:errmsg = 'fugitive: '.message return 'echoerr v:errmsg' endif let destination = s:repo().tree(destination) if isdirectory(destination) let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.') endif if s:buffer().commit() == '' return 'saveas! '.s:fnameescape(destination) else return 'file '.s:fnameescape(s:repo.translate(':0:'.destination) endif endfunction function! s:MoveComplete(A,L,P) if a:A =~ '^/' return s:repo().superglob(a:A) else return split(glob(a:A.'*'),"\n") endif endfunction function! s:Remove(force) if s:buffer().commit() ==# '' let cmd = ['rm'] elseif s:buffer().commit() ==# '0' let cmd = ['rm','--cached'] else let v:errmsg = 'fugitive: rm not supported here' return 'echoerr v:errmsg' endif if a:force let cmd += ['--force'] endif let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo()) if v:shell_error let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)') return 'echoerr '.string(v:errmsg) else return 'bdelete'.(a:force ? '!' : '') endif endfunction augroup fugitive_remove autocmd! autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' | \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(0,)" | \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(0)" | \ endif augroup END " }}}1 " Gblame {{{1 augroup fugitive_blame autocmd! autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif autocmd Syntax fugitiveblame call s:BlameSyntax() autocmd User Fugitive if s:buffer().type('file', 'blob') | exe "command! -buffer -bar -bang -range=0 Gblame :execute s:Blame(0,,,,)" | endif augroup END function! s:Blame(bang,line1,line2,count) abort try if s:buffer().path() == '' call s:throw('file or blob required') endif let git_dir = s:repo().dir() let cmd = ['--no-pager', 'blame', '--show-number'] if strlen(s:buffer().commit()) == 40 let cmd += [s:buffer().commit()] else let cmd += ['--contents', '-'] endif let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo()) try let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' if !s:repo().bare() let dir = getcwd() execute cd.' `=s:repo().tree()`' endif if a:count execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g') else let temp = tempname().'.fugitiveblame' silent! exe '%write !'.basecmd.' > '.temp.' 2> '.temp if v:shell_error call s:throw(join(readfile(temp),"\n")) endif let bufnr = bufnr('') let restore = 'call setbufvar('.bufnr.',"&scrollbind",0)' if &l:wrap let restore .= '|call setbufvar('.bufnr.',"&wrap",1)' endif if &l:foldenable let restore .= '|call setbufvar('.bufnr.',"&foldenable",1)' endif let winnr = winnr() windo set noscrollbind exe winnr.'wincmd w' setlocal scrollbind nowrap nofoldenable let top = line('w0') + &scrolloff let current = line('.') exe 'leftabove vsplit '.temp let b:git_dir = git_dir let b:fugitive_type = 'blame' let b:fugitive_blamed_bufnr = bufnr let b:fugitive_restore = restore call s:Detect() execute top normal! zt execute current execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1) setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame nnoremap q :bdelete nnoremap :exe BlameJump() nnoremap o :exe Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+')) nnoremap O :exe Edit("tabedit", matchstr(getline('.'),'\x\+')) syncbind endif finally if exists('l:dir') execute cd.' `=dir`' endif endtry return '' catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction function! s:BlameJump() abort let commit = matchstr(getline('.'),'^\^\=\zs\x\+') if commit =~# '^0\+$' let commit = ':0' endif let lnum = matchstr(getline('.'),'\d\+\ze (') let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ (') if path ==# '' let path = s:buffer(b:fugitive_blamed_bufnr).path() endif let offset = line('.') - line('w0') let bufnr = bufnr('%') let winnr = bufwinnr(b:fugitive_blamed_bufnr) if winnr > 0 exe winnr.'wincmd w' endif execute s:Edit('edit',commit.':'.path) if winnr > 0 exe bufnr.'bdelete' endif Gblame execute lnum let delta = line('.') - line('w0') - offset if delta > 0 execute 'norm! 'delta."\" elseif delta < 0 execute 'norm! '(-delta)."\" endif syncbind return '' endfunction function! s:BlameSyntax() abort let b:current_syntax = 'fugitiveblame' syn match FugitiveblameBoundary "^\^" syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation syn match FugitiveblameLineNumber " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\= (\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%( (\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation hi def link FugitiveblameBoundary Keyword hi def link FugitiveblameHash Identifier hi def link FugitiveblameUncommitted Function hi def link FugitiveblameTime PreProc hi def link FugitiveblameLineNumber Number hi def link FugitiveblameOriginalFile String hi def link FugitiveblameOriginalLineNumber Float hi def link FugitiveblameDelimiter Delimiter hi def link FugitiveblameNotCommittedYet Comment endfunction " }}}1 " File access {{{1 function! s:ReplaceCmd(cmd) abort let fn = bufname('') let tmp = tempname() let aw = &autowrite try set noautowrite silent exe '!'.escape(a:cmd,'%#') ' > '.tmp finally let &autowrite = aw endtry silent exe 'keepalt file '.tmp silent edit! silent exe 'keepalt file '.s:fnameescape(fn) call delete(tmp) silent exe 'doau BufReadPost '.s:fnameescape(fn) endfunction function! s:BufReadIndex() try let b:git_dir = s:repo().dir() let b:fugitive_type = 'index' setlocal noro ma call s:ReplaceCmd(s:repo().git_command('ls-files','--stage')) setlocal ro noma nomod nomodeline ft=git catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction function! s:FileRead() try let repo = s:repo(s:ExtractGitDir(expand(''))) let path = s:sub(s:sub(matchstr(expand(''),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&') let hash = repo.rev_parse(path) if path =~ '^:' let type = 'blob' else let type = repo.git_chomp('cat-file','-t',hash) endif " TODO: use count, if possible return "read !".escape(repo.git_command('cat-file',type,hash),'%#\') catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction function! s:BufReadIndexFile() try let b:fugitive_type = 'blob' let b:git_dir = s:repo().dir() call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1())) return '' catch /^fugitive: rev-parse/ silent exe 'doau BufNewFile '.s:fnameescape(bufname('')) return '' catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction function! s:BufWriteIndexFile() let tmp = tempname() try let path = matchstr(expand(''),'//\d/\zs.*') let stage = matchstr(expand(''),'//\zs\d') silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp let sha1 = readfile(tmp)[0] let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+') if old_mode == '' let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644' endif let info = old_mode.' '.sha1.' '.stage."\t".path call writefile([info],tmp) let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp) if v:shell_error == 0 setlocal nomodified return '' else return 'echoerr '.string('fugitive: '.error) endif finally call delete(tmp) endtry endfunction function! s:BufReadObject() try setlocal noro ma let b:git_dir = s:repo().dir() let hash = s:buffer().sha1() if !exists("b:fugitive_type") let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash) endif if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$' return "echoerr 'fugitive: unrecognized git type'" endif let firstline = getline('.') if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob' let b:fugitive_display_format = +getbufvar('#','fugitive_display_format') endif let pos = getpos('.') silent %delete setlocal endofline if b:fugitive_type == 'tree' let b:fugitive_display_format = b:fugitive_display_format % 2 if b:fugitive_display_format call s:ReplaceCmd(s:repo().git_command('ls-tree',hash)) else call s:ReplaceCmd(s:repo().git_command('show',hash)) endif elseif b:fugitive_type == 'tag' let b:fugitive_display_format = b:fugitive_display_format % 2 if b:fugitive_display_format call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) else call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash)) endif elseif b:fugitive_type == 'commit' let b:fugitive_display_format = b:fugitive_display_format % 2 if b:fugitive_display_format call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) else 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)) call search('^parent ') silent s/\%(^parent\)\@\)\=$','W',line('.')+3) silent delete end 1 endif elseif b:fugitive_type ==# 'blob' call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) endif call setpos('.',pos) setlocal ro noma nomod nomodeline if b:fugitive_type !=# 'blob' set filetype=git nnoremap a :let b:fugitive_display_format += 1exe BufReadObject() nnoremap i :let b:fugitive_display_format -= 1exe BufReadObject() else call s:JumpInit() endif return '' catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction augroup fugitive_files autocmd! autocmd BufReadCmd *.git/index exe s:BufReadIndex() autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead() autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile() autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile() autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject() autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead() autocmd FileType git call s:JumpInit() augroup END " }}}1 " Go to file {{{1 function! s:JumpInit() abort nnoremap :exe GF("edit") if !&modifiable nnoremap o :exe GF("split") nnoremap O :exe GF("tabedit") nnoremap P :exe Edit('edit',buffer().commit().'^'.v:count1.buffer().path(':')) nnoremap ~ :exe Edit('edit',buffer().commit().'~'.v:count1.buffer().path(':')) nnoremap C :exe Edit('edit',buffer().containing_commit()) nnoremap c :exe Edit('pedit',buffer().containing_commit()) endif endfunction function! s:GF(mode) abort try let buffer = s:buffer() let myhash = buffer.sha1() if buffer.type('index') if getline('.') =~# '^\d\{6\} \x\{40\} \d\t' let ref = matchstr(getline('.'),'\x\{40\}') let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':') return s:Edit(a:mode,file) endif elseif buffer.type('tree') let showtree = (getline(1) =~# '^tree ' && getline(2) == "") if showtree && line('.') == 1 return "" elseif showtree && line('.') > 2 return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(getline('.'),'/$','')) elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t' return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')) endif elseif buffer.type('blob') let ref = expand("") try let sha1 = buffer.repo().rev_parse(ref) catch /^fugitive:/ endtry if exists('sha1') return s:Edit(a:mode,ref) endif else let showtree = (getline(1) =~# '^tree ' && getline(2) == "") if getline('.') =~# '^ref: ' let ref = strpart(getline('.'),5) elseif getline('.') =~# '^parent \x\{40\}\>' let ref = matchstr(getline('.'),'\x\{40\}') let line = line('.') let parent = 0 while getline(line) =~# '^parent ' let parent += 1 let line -= 1 endwhile return s:Edit(a:mode,ref) elseif getline('.') =~ '^tree \x\{40\}$' let ref = matchstr(getline('.'),'\x\{40\}') if s:repo().rev_parse(myhash.':') == ref let ref = myhash.':' endif return s:Edit(a:mode,ref) elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$' let ref = matchstr(getline('.'),'\x\{40\}') let type = matchstr(getline(line('.')+1),'type \zs.*') elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$' return '' elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>' let ref = matchstr(getline('.'),'\x\{40\}') echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*') elseif getline('.') =~# '^[+-]\{3\} [ab/]' let ref = getline('.')[4:] elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$' let ref = getline('.') else let ref = '' endif if myhash != '' let ref = s:sub(ref,'^a/',myhash.'^:') let ref = s:sub(ref,'^b/',myhash.':') endif if ref == '/dev/null' " Empty blob let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391' endif if ref != "" return s:Edit(a:mode,ref) endif endif return '' catch /^fugitive:/ return 'echoerr v:errmsg' endtry endfunction " }}}1 " vim:set ft=vim ts=8 sw=2 sts=2: