1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer: Tim Pope <http://tpo.pe/>
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'
33 return '"'.s:gsub(s:gsub(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,'\\','/')
68 let rev = s:sub(s:buffer().rev(), '^/', '')
70 return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
75 function! s:add_methods(namespace, method_names) abort
76 for name in a:method_names
77 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
82 function! s:command(definition) abort
83 let s:commands += [a:definition]
86 function! s:define_commands()
87 for command in s:commands
88 exe 'command! -buffer '.command
92 augroup fugitive_utility
94 autocmd User Fugitive call s:define_commands()
97 let s:abstract_prototype = {}
100 " Initialization {{{1
102 function! fugitive#is_git_dir(path) abort
103 let path = s:sub(a:path, '[\/]$', '') . '/'
104 return isdirectory(path.'objects') && isdirectory(path.'refs') && getfsize(path.'HEAD') > 10
107 function! fugitive#extract_git_dir(path) abort
108 if s:shellslash(a:path) =~# '^fugitive://.*//'
109 return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
111 let root = s:shellslash(simplify(fnamemodify(a:path, ':p:s?[\/]$??')))
113 while root !=# previous
114 let dir = s:sub(root, '[\/]$', '') . '/.git'
115 let type = getftype(dir)
116 if type ==# 'dir' && fugitive#is_git_dir(dir)
118 elseif type ==# 'link' && fugitive#is_git_dir(dir)
120 elseif type !=# '' && filereadable(dir)
121 let line = get(readfile(dir, '', 1), 0, '')
122 if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
123 return simplify(root.'/'.line[8:-1])
124 elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
127 elseif fugitive#is_git_dir(root)
131 let root = fnamemodify(root, ':h')
136 function! s:Detect(path)
137 if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
140 if !exists('b:git_dir')
141 let dir = fugitive#extract_git_dir(a:path)
146 if exists('b:git_dir')
147 silent doautocmd User Fugitive
148 cnoremap <buffer> <expr> <C-R><C-G> <SID>recall()
149 nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
150 let buffer = fugitive#buffer()
151 if expand('%:p') =~# '//'
152 call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
154 " Look for tags file in .git dir and add them to &tags
155 " See http://tbaggery.com/2011/08/08/effortless-ctags-with-git.html
156 let tagsfile = b:git_dir.'/tags'
157 if stridx(buffer.getvar('&tags'), escape(tagsfile, ', ')) == -1
158 if filereadable(tagsfile)
159 call buffer.setvar('&tags', escape(tagsfile, ', ').','.buffer.getvar('&tags'))
162 let tagsfile = b:git_dir.'/'.&filetype.'.tags'
163 if filereadable(tagsfile)
164 call buffer.setvar('&tags', escape(tagsfile, ', ').','.buffer.getvar('&tags'))
173 autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
174 autocmd FileType netrw call s:Detect(expand('%:p'))
175 autocmd User NERDTreeInit,NERDTreeNewRoot call s:Detect(b:NERDTreeRoot.path.str())
176 autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
177 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
183 let s:repo_prototype = {}
186 function! s:repo(...) abort
187 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
189 if has_key(s:repos, dir)
190 let repo = get(s:repos, dir)
192 let repo = {'git_dir': dir}
193 let s:repos[dir] = repo
195 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
197 call s:throw('not a git repository: '.expand('%:p'))
200 function! fugitive#repo(...)
201 return call('s:repo', a:000)
204 function! s:repo_dir(...) dict abort
205 return join([self.git_dir]+a:000,'/')
208 function! s:repo_configured_tree() dict abort
209 if !has_key(self,'_tree')
211 if filereadable(self.dir('config'))
212 let config = readfile(self.dir('config'),'',10)
213 call filter(config,'v:val =~# "^\\s*worktree *="')
215 let self._tree = matchstr(config[0], '= *\zs.*')
219 if self._tree =~# '^\.'
220 return simplify(self.dir(self._tree))
226 function! s:repo_tree(...) dict abort
227 if self.dir() =~# '/\.git$'
228 let dir = self.dir()[0:-6]
230 let dir = self.configured_tree()
233 call s:throw('no work tree')
235 return join([dir]+a:000,'/')
239 function! s:repo_bare() dict abort
240 if self.dir() =~# '/\.git$'
243 return self.configured_tree() ==# ''
247 function! s:repo_translate(spec) dict abort
248 if a:spec ==# '.' || a:spec ==# '/.'
249 return self.bare() ? self.dir() : self.tree()
250 elseif a:spec =~# '^/\=\.git$' && self.bare()
252 elseif a:spec =~# '^/\=\.git/'
253 return self.dir(s:sub(a:spec, '^/=\.git/', ''))
254 elseif a:spec =~# '^/'
255 return self.tree().a:spec
256 elseif a:spec =~# '^:[0-3]:'
257 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
258 elseif a:spec ==# ':'
259 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
260 return fnamemodify($GIT_INDEX_FILE,':p')
262 return self.dir('index')
264 elseif a:spec =~# '^:/'
265 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
266 return 'fugitive://'.self.dir().'//'.ref
267 elseif a:spec =~# '^:'
268 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
269 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
270 return self.dir(a:spec)
271 elseif filereadable(self.dir('refs/'.a:spec))
272 return self.dir('refs/'.a:spec)
273 elseif filereadable(self.dir('refs/tags/'.a:spec))
274 return self.dir('refs/tags/'.a:spec)
275 elseif filereadable(self.dir('refs/heads/'.a:spec))
276 return self.dir('refs/heads/'.a:spec)
277 elseif filereadable(self.dir('refs/remotes/'.a:spec))
278 return self.dir('refs/remotes/'.a:spec)
279 elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
280 return self.dir('refs/remotes/'.a:spec,'/HEAD')
283 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
284 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
285 return 'fugitive://'.self.dir().'//'.ref.path
287 return self.tree(a:spec)
292 function! s:repo_head(...) dict abort
293 let head = s:repo().head_ref()
296 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
297 elseif head =~# '^\x\{40\}$'
298 " truncate hash to a:1 characters if we're in detached head mode
299 let len = a:0 ? a:1 : 0
300 let branch = len ? head[0:len-1] : ''
306 call s:add_methods('repo',['dir','configured_tree','tree','bare','translate','head'])
308 function! s:repo_git_command(...) dict abort
309 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
310 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
313 function! s:repo_git_chomp(...) dict abort
314 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
317 function! s:repo_git_chomp_in_tree(...) dict abort
318 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
321 execute cd.'`=s:repo().tree()`'
322 return call(s:repo().git_chomp, a:000, s:repo())
328 function! s:repo_rev_parse(rev) dict abort
329 let hash = self.git_chomp('rev-parse','--verify',a:rev)
330 if hash =~ '\<\x\{40\}$'
331 return matchstr(hash,'\<\x\{40\}$')
333 call s:throw('rev-parse '.a:rev.': '.hash)
336 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
338 function! s:repo_dirglob(base) dict abort
339 let base = s:sub(a:base,'^/','')
340 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
341 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
345 function! s:repo_superglob(base) dict abort
346 if a:base =~# '^/' || a:base !~# ':'
349 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
350 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
351 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
355 let base = s:sub(a:base,'^/','')
356 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
357 call map(matches,'s:shellslash(v:val)')
358 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
359 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
360 let results += matches
364 elseif a:base =~# '^:'
365 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
366 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
367 if a:base !~# '^:[0-3]\%(:\|$\)'
368 call filter(entries,'v:val[1] == "0"')
369 call map(entries,'v:val[2:-1]')
371 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
375 let tree = matchstr(a:base,'.*[:/]')
376 let entries = split(self.git_chomp('ls-tree',tree),"\n")
377 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
378 call map(entries,'tree.s:sub(v:val,".*\t","")')
379 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
383 call s:add_methods('repo',['dirglob','superglob'])
385 function! s:repo_config(conf) dict abort
386 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
389 function! s:repo_user() dict abort
390 let username = s:repo().config('user.name')
391 let useremail = s:repo().config('user.email')
392 return username.' <'.useremail.'>'
395 function! s:repo_aliases() dict abort
396 if !has_key(self,'_aliases')
397 let self._aliases = {}
398 for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
399 let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
405 call s:add_methods('repo',['config', 'user', 'aliases'])
407 function! s:repo_keywordprg() dict abort
408 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
409 if has('gui_running') && !has('win32')
410 return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
412 return g:fugitive_git_executable . args . ' show'
416 call s:add_methods('repo',['keywordprg'])
421 let s:buffer_prototype = {}
423 function! s:buffer(...) abort
424 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
425 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
426 if buffer.getvar('git_dir') !=# ''
429 call s:throw('not a git repository: '.expand('%:p'))
432 function! fugitive#buffer(...) abort
433 return s:buffer(a:0 ? a:1 : '%')
436 function! s:buffer_getvar(var) dict abort
437 return getbufvar(self['#'],a:var)
440 function! s:buffer_setvar(var,value) dict abort
441 return setbufvar(self['#'],a:var,a:value)
444 function! s:buffer_getline(lnum) dict abort
445 return getbufline(self['#'],a:lnum)[0]
448 function! s:buffer_repo() dict abort
449 return s:repo(self.getvar('git_dir'))
452 function! s:buffer_type(...) dict abort
453 if self.getvar('fugitive_type') != ''
454 let type = self.getvar('fugitive_type')
455 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
457 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
459 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
461 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
463 elseif isdirectory(self.spec())
464 let type = 'directory'
465 elseif self.spec() == ''
471 return !empty(filter(copy(a:000),'v:val ==# type'))
479 function! s:buffer_spec() dict abort
480 let bufname = bufname(self['#'])
482 for i in split(bufname,'[^:]\zs\\')
483 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
485 return s:shellslash(fnamemodify(retval,':p'))
490 function! s:buffer_spec() dict abort
491 let bufname = bufname(self['#'])
492 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
497 function! s:buffer_name() dict abort
501 function! s:buffer_commit() dict abort
502 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
505 function! s:buffer_path(...) dict abort
506 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
508 let rev = s:sub(rev,'\w*','')
509 elseif self.repo().bare()
510 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
512 let rev = self.spec()[strlen(self.repo().tree()) : -1]
514 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
517 function! s:buffer_rev() dict abort
518 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
520 return ':'.rev[0].':'.rev[2:-1]
522 return s:sub(rev,'/',':')
523 elseif self.spec() =~ '\.git/index$'
525 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
526 return self.spec()[strlen(self.repo().dir())+1 : -1]
528 return self.path('/')
532 function! s:buffer_sha1() dict abort
533 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
534 return self.repo().rev_parse(self.rev())
540 function! s:buffer_expand(rev) dict abort
541 if a:rev =~# '^:[0-3]$'
542 let file = a:rev.self.path(':')
543 elseif a:rev =~# '^[-:]/$'
544 let file = '/'.self.path()
545 elseif a:rev =~# '^-'
546 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
547 elseif a:rev =~# '^@{'
548 let file = 'HEAD'.a:rev.self.path(':')
549 elseif a:rev =~# '^[~^]'
550 let commit = s:sub(self.commit(),'^\d=$','HEAD')
551 let file = commit.a:rev.self.path(':')
555 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
558 function! s:buffer_containing_commit() dict abort
559 if self.commit() =~# '^\d$'
561 elseif self.commit() =~# '.'
568 function! s:buffer_up(...) dict abort
570 let c = a:0 ? a:1 : 1
576 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
578 elseif rev =~# '^/\|:.*/'
579 let rev = s:sub(rev, '.*\zs/.*', '')
581 let rev = matchstr(rev, '^[^:]*:')
592 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
597 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
599 function! s:ExecuteInTree(cmd) abort
600 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
603 execute cd.'`=s:repo().tree()`'
610 function! s:Git(bang,cmd) abort
612 return s:Edit('edit',1,a:cmd)
614 let git = s:repo().git_command()
615 if has('gui_running') && !has('win32')
616 let git .= ' --no-pager'
618 let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
619 call s:ExecuteInTree('!'.git.' '.cmd)
620 call fugitive#reload_status()
621 return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
624 function! s:GitComplete(A,L,P) abort
625 if !exists('s:exec_path')
626 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
628 let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
629 if a:L =~ ' [[:alnum:]-]\+ '
630 return s:repo().superglob(a:A)
632 return sort(cmds+keys(s:repo().aliases()))
634 return filter(sort(cmds+keys(s:repo().aliases())),'v:val[0:strlen(a:A)-1] ==# a:A')
641 function! s:DirComplete(A,L,P) abort
642 let matches = s:repo().dirglob(a:A)
646 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>)`")
647 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>)`")
652 call s:command("-bar Gstatus :execute s:Status()")
654 function! s:Status() abort
658 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
660 return 'echoerr v:errmsg'
665 function! fugitive#reload_status() abort
666 let mytab = tabpagenr()
667 for tab in [mytab] + range(1,tabpagenr('$'))
668 for winnr in range(1,tabpagewinnr(tab,'$'))
669 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
670 execute 'tabnext '.tab
672 execute winnr.'wincmd w'
677 call s:BufReadIndex()
680 if exists('restorewinnr')
683 execute 'tabnext '.mytab
690 function! s:stage_info(lnum) abort
691 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
693 while lnum && getline(lnum) !~# '^#.*:$'
698 elseif getline(lnum+1) =~# '^# .*"git \%(reset\|rm --cached\) ' || getline(lnum) ==# '# Changes to be committed:'
699 return [matchstr(filename, ': *\zs.*'), 'staged']
700 elseif getline(lnum+2) =~# '^# .*"git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
701 return [matchstr(filename, ': *\zs.*'), 'unstaged']
702 elseif getline(lnum+1) =~# '^# .*"git add/rm ' || getline(lnum) ==# '# Unmerged paths:'
703 return [matchstr(filename, ': *\zs.*'), 'unmerged']
705 return [filename, 'untracked']
709 function! s:StageNext(count)
710 for i in range(a:count)
711 call search('^#\t.*','W')
716 function! s:StagePrevious(count)
717 if line('.') == 1 && exists(':CtrlP')
718 return 'CtrlP '.fnameescape(s:repo().tree())
720 for i in range(a:count)
721 call search('^#\t.*','Wbe')
727 function! s:StageReloadSeek(target,lnum1,lnum2)
729 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
730 if f !=# '' | let jump = f | endif
731 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
732 if f !=# '' | let jump = f | endif
736 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
739 function! s:StageDiff(diff) abort
740 let [filename, section] = s:stage_info(line('.'))
741 if filename ==# '' && section ==# 'staged'
742 return 'Git! diff --cached'
743 elseif filename ==# ''
745 elseif filename =~# ' -> '
746 let [old, new] = split(filename,' -> ')
747 execute 'Gedit '.s:fnameescape(':0:'.new)
748 return a:diff.' HEAD:'.s:fnameescape(old)
749 elseif section ==# 'staged'
750 execute 'Gedit '.s:fnameescape(':0:'.filename)
753 execute 'Gedit '.s:fnameescape('/'.filename)
758 function! s:StageDiffEdit() abort
759 let [filename, section] = s:stage_info(line('.'))
760 let arg = (filename ==# '' ? '.' : filename)
761 if section ==# 'staged'
762 return 'Git! diff --cached '.s:shellesc(arg)
763 elseif section ==# 'untracked'
765 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
769 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
770 call search('^# .*:$','W')
773 call s:StageReloadSeek(arg,line('.'),line('.'))
777 return 'Git! diff '.s:shellesc(arg)
781 function! s:StageToggle(lnum1,lnum2) abort
784 for lnum in range(a:lnum1,a:lnum2)
785 let [filename, section] = s:stage_info(lnum)
787 if getline('.') =~# '^# .*:$'
788 if section ==# 'staged'
789 call repo.git_chomp_in_tree('reset','-q')
792 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
793 call search('^# .*:$','W')
796 elseif section ==# 'unstaged'
797 call repo.git_chomp_in_tree('add','-u')
800 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
801 call search('^# .*:$','W')
805 call repo.git_chomp_in_tree('add','.')
808 call search('^# .*:$','W')
815 if !exists('first_filename')
816 let first_filename = filename
819 if filename =~ ' -> '
820 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
821 let filename = cmd[-1]
822 elseif section ==# 'staged'
823 let cmd = ['reset','-q','--',filename]
824 elseif getline(lnum) =~# '^#\tdeleted:'
825 let cmd = ['rm','--',filename]
826 elseif getline(lnum) =~# '^#\tmodified:'
827 let cmd = ['add','--',filename]
829 let cmd = ['add','-A','--',filename]
831 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
833 if exists('first_filename')
834 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
836 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
838 return 'echoerr v:errmsg'
843 function! s:StagePatch(lnum1,lnum2) abort
847 for lnum in range(a:lnum1,a:lnum2)
848 let [filename, section] = s:stage_info(lnum)
849 if getline('.') =~# '^# .*:$' && section ==# 'staged'
850 return 'Git reset --patch'
851 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
852 return 'Git add --patch'
853 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
854 return 'Git add -N .'
855 elseif filename ==# ''
858 if !exists('first_filename')
859 let first_filename = filename
862 if filename =~ ' -> '
863 let reset += [split(filename,' -> ')[1]]
864 elseif section ==# 'staged'
865 let reset += [filename]
866 elseif getline(lnum) !~# '^#\tdeleted:'
867 let add += [filename]
872 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
875 execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
877 if exists('first_filename')
881 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
884 return 'echoerr v:errmsg'
892 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
894 function! s:Commit(args) abort
895 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
897 let msgfile = s:repo().dir('COMMIT_EDITMSG')
898 let outfile = tempname()
899 let errorfile = tempname()
902 execute cd.s:fnameescape(s:repo().tree())
905 let old_editor = $GIT_EDITOR
906 let $GIT_EDITOR = 'false'
908 let command = 'env GIT_EDITOR=false '
910 let command .= s:repo().git_command('commit').' '.a:args
912 silent execute '!('.command.' > '.outfile.') >& '.errorfile
913 elseif a:args =~# '\%(^\| \)--interactive\>'
914 execute '!'.command.' 2> '.errorfile
916 silent execute '!'.command.' > '.outfile.' 2> '.errorfile
921 if !has('gui_running')
925 if filereadable(outfile)
926 for line in readfile(outfile)
932 let errors = readfile(errorfile)
933 let error = get(errors,-2,get(errors,-1,'!'))
934 if error =~# '\<false''\=\.$'
936 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
937 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
938 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
939 let args = '-F '.s:shellesc(msgfile).' '.args
940 if args !~# '\%(^\| \)--cleanup\>'
941 let args = '--cleanup=strip '.args
943 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
944 execute 'keepalt edit '.s:fnameescape(msgfile)
945 elseif s:buffer().type() ==# 'index'
946 execute 'keepalt edit '.s:fnameescape(msgfile)
947 execute (search('^#','n')+1).'wincmd+'
948 setlocal nopreviewwindow
950 execute 'keepalt split '.s:fnameescape(msgfile)
952 let b:fugitive_commit_arguments = args
953 setlocal bufhidden=wipe filetype=gitcommit
962 return 'echoerr v:errmsg'
964 if exists('old_editor')
965 let $GIT_EDITOR = old_editor
968 call delete(errorfile)
969 call fugitive#reload_status()
973 function! s:CommitComplete(A,L,P) abort
974 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
975 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']
976 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
978 return s:repo().superglob(a:A)
982 function! s:FinishCommit()
983 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
985 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
986 return s:Commit(args)
991 augroup fugitive_commit
993 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
999 if !exists('g:fugitive_summary_format')
1000 let g:fugitive_summary_format = '%s'
1003 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1004 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1005 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
1006 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gllog :execute s:Log('lgrep<bang>',<f-args>)")
1008 function! s:Grep(cmd,bang,arg) abort
1009 let grepprg = &grepprg
1010 let grepformat = &grepformat
1011 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1014 execute cd.'`=s:repo().tree()`'
1015 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1016 let &grepformat = '%f:%l:%m'
1017 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1018 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1020 if bufname(entry.bufnr) =~ ':'
1021 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1023 elseif a:arg =~# '\%(^\| \)--cached\>'
1024 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1029 call setloclist(0, list, 'r')
1031 call setqflist(list, 'r')
1033 if !a:bang && !empty(list)
1034 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1036 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1039 let &grepprg = grepprg
1040 let &grepformat = grepformat
1045 function! s:Log(cmd,...)
1046 let path = s:buffer().path('/')
1047 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1050 let cmd = ['--no-pager', 'log', '--no-color']
1051 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1052 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1053 if s:buffer().commit() =~# '\x\{40\}'
1054 let cmd += [s:buffer().commit()]
1055 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1056 let cmd += [s:buffer().path()[5:-1]]
1059 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1061 let cmd += ['--',path[1:-1]]
1063 let grepformat = &grepformat
1064 let grepprg = &grepprg
1065 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1068 execute cd.'`=s:repo().tree()`'
1069 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1070 let &grepformat = '%f::%m'
1073 let &grepformat = grepformat
1074 let &grepprg = grepprg
1080 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
1082 function! s:Edit(cmd,bang,...) abort
1083 let buffer = s:buffer()
1085 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1088 let mywinnr = winnr()
1089 for winnr in range(winnr('$'),1,-1)
1090 if winnr != mywinnr && getwinvar(winnr,'&diff')
1091 execute winnr.'wincmd w'
1101 let args = s:gsub(a:0 ? a:1 : '', '\\@<!%(\\\\)*\zs[%#]', '\=s:buffer().expand(submatch(0))')
1103 let git = buffer.repo().git_command()
1104 let last = line('$')
1105 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1107 silent execute '1,'.last.'delete_'
1109 call fugitive#reload_status()
1111 return 'redraw|echo '.string(':!'.git.' '.args)
1113 let temp = resolve(tempname())
1114 let s:temp_files[temp] = buffer.repo().dir()
1115 silent execute a:cmd.' '.temp
1116 if a:cmd =~# 'pedit'
1119 let echo = s:Edit('read',1,args)
1121 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1122 if getline(1) !~# '^diff '
1123 setlocal readonly nomodifiable
1125 if a:cmd =~# 'pedit'
1136 let file = buffer.expand(a:1)
1137 elseif expand('%') ==# ''
1139 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1140 let file = buffer.path(':')
1142 let file = buffer.path('/')
1145 let file = buffer.repo().translate(file)
1147 return 'echoerr v:errmsg'
1150 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1152 return a:cmd.' '.s:fnameescape(file)
1156 function! s:EditComplete(A,L,P) abort
1157 return s:repo().superglob(a:A)
1160 function! s:EditRunComplete(A,L,P) abort
1162 return s:GitComplete(a:A,a:L,a:P)
1164 return s:repo().superglob(a:A)
1168 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1169 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1170 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1171 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1172 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1173 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1174 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1179 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1180 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1181 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1183 function! s:Write(force,...) abort
1184 if exists('b:fugitive_commit_arguments')
1185 return 'write|bdelete'
1186 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1188 elseif s:buffer().type() == 'index'
1190 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1191 let filename = getline(4)[6:-1]
1194 setlocal buftype=nowrite
1195 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1196 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1198 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1201 let v:errmsg = split(err,"\n")[0]
1202 return 'echoerr v:errmsg'
1206 return 'Gedit '.fnameescape(filename)
1209 let mytab = tabpagenr()
1210 let mybufnr = bufnr('')
1211 let path = a:0 ? a:1 : s:buffer().path()
1212 if path =~# '^:\d\>'
1213 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1215 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1216 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) !=# ''
1217 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1218 return 'echoerr v:errmsg'
1220 let file = s:repo().translate(path)
1222 for nr in range(1,bufnr('$'))
1223 if fnamemodify(bufname(nr),':p') ==# file
1228 if treebufnr > 0 && treebufnr != bufnr('')
1229 let temp = tempname()
1230 silent execute '%write '.temp
1231 for tab in [mytab] + range(1,tabpagenr('$'))
1232 for winnr in range(1,tabpagewinnr(tab,'$'))
1233 if tabpagebuflist(tab)[winnr-1] == treebufnr
1234 execute 'tabnext '.tab
1236 execute winnr.'wincmd w'
1237 let restorewinnr = 1
1240 let lnum = line('.')
1241 let last = line('$')
1242 silent execute '$read '.temp
1243 silent execute '1,'.last.'delete_'
1248 if exists('restorewinnr')
1251 execute 'tabnext '.mytab
1257 call writefile(readfile(temp,'b'),file,'b')
1260 execute 'write! '.s:fnameescape(s:repo().translate(path))
1264 let error = s:repo().git_chomp_in_tree('add', '--force', file)
1266 let error = s:repo().git_chomp_in_tree('add', file)
1269 let v:errmsg = 'fugitive: '.error
1270 return 'echoerr v:errmsg'
1272 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1276 let one = s:repo().translate(':1:'.path)
1277 let two = s:repo().translate(':2:'.path)
1278 let three = s:repo().translate(':3:'.path)
1279 for nr in range(1,bufnr('$'))
1280 let name = fnamemodify(bufname(nr), ':p')
1281 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1282 execute nr.'bdelete'
1287 let zero = s:repo().translate(':0:'.path)
1288 for tab in range(1,tabpagenr('$'))
1289 for winnr in range(1,tabpagewinnr(tab,'$'))
1290 let bufnr = tabpagebuflist(tab)[winnr-1]
1291 let bufname = fnamemodify(bufname(bufnr), ':p')
1292 if bufname ==# zero && bufnr != mybufnr
1293 execute 'tabnext '.tab
1295 execute winnr.'wincmd w'
1296 let restorewinnr = 1
1299 let lnum = line('.')
1300 let last = line('$')
1301 silent execute '$read '.s:fnameescape(file)
1302 silent execute '1,'.last.'delete_'
1307 if exists('restorewinnr')
1310 execute 'tabnext '.mytab
1316 call fugitive#reload_status()
1320 function! s:Wq(force,...) abort
1321 let bang = a:force ? '!' : ''
1322 if exists('b:fugitive_commit_arguments')
1325 let result = call(s:function('s:Write'),[a:force]+a:000)
1326 if result =~# '^\%(write\|wq\|echoerr\)'
1327 return s:sub(result,'^write','wq')
1329 return result.'|quit'.bang
1336 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
1337 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
1338 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
1340 augroup fugitive_diff
1342 autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif
1343 autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff() | endif
1346 function! s:diff_window_count()
1348 for nr in range(1,winnr('$'))
1349 let c += getwinvar(nr,'&diff')
1354 function! s:diffthis()
1356 let w:fugitive_diff_restore = 'setlocal nodiff noscrollbind'
1357 let w:fugitive_diff_restore .= ' scrollopt=' . &l:scrollopt
1358 let w:fugitive_diff_restore .= &l:wrap ? ' wrap' : ' nowrap'
1359 let w:fugitive_diff_restore .= ' foldmethod=' . &l:foldmethod
1360 let w:fugitive_diff_restore .= ' foldcolumn=' . &l:foldcolumn
1361 if has('cursorbind')
1362 let w:fugitive_diff_restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1368 function! s:diffoff()
1369 if exists('w:fugitive_diff_restore')
1370 execute w:fugitive_diff_restore
1371 unlet w:fugitive_diff_restore
1377 function! s:diffoff_all(dir)
1378 for nr in range(1,winnr('$'))
1379 if getwinvar(nr,'&diff')
1381 execute nr.'wincmd w'
1382 let restorewinnr = 1
1384 if exists('b:git_dir') && b:git_dir ==# a:dir
1391 function! s:buffer_compare_age(commit) dict abort
1392 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1393 let my_score = get(scores,':'.self.commit(),0)
1394 let their_score = get(scores,':'.a:commit,0)
1395 if my_score || their_score
1396 return my_score < their_score ? -1 : my_score != their_score
1397 elseif self.commit() ==# a:commit
1400 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1401 if base ==# self.commit()
1403 elseif base ==# a:commit
1406 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1407 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1408 return my_time < their_time ? -1 : my_time != their_time
1411 call s:add_methods('buffer',['compare_age'])
1413 function! s:Diff(bang,...)
1414 let split = a:bang ? 'split' : 'vsplit'
1415 if exists(':DiffGitCached')
1416 return 'DiffGitCached'
1417 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1419 execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1420 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1423 execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1424 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1433 let file = s:buffer().path('/')
1435 let file = s:buffer().path(':0:')
1436 elseif a:1 =~# '^:/.'
1438 let file = s:repo().rev_parse(a:1).s:buffer().path(':')
1440 return 'echoerr v:errmsg'
1443 let file = s:buffer().expand(a:1)
1445 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1446 let file = file.s:buffer().path(':')
1449 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1452 let spec = s:repo().translate(file)
1453 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1454 if s:buffer().compare_age(commit) < 0
1455 execute 'rightbelow '.split.' '.s:fnameescape(spec)
1457 execute 'leftabove '.split.' '.s:fnameescape(spec)
1464 return 'echoerr v:errmsg'
1469 " Gmove, Gremove {{{1
1471 function! s:Move(force,destination)
1472 if a:destination =~# '^/'
1473 let destination = a:destination[1:-1]
1475 let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
1476 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1477 let destination = destination[strlen(s:repo().tree('')):-1]
1480 if isdirectory(s:buffer().spec())
1481 " Work around Vim parser idiosyncrasy
1482 let discarded = s:buffer().setvar('&swapfile',0)
1484 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1486 let v:errmsg = 'fugitive: '.message
1487 return 'echoerr v:errmsg'
1489 let destination = s:repo().tree(destination)
1490 if isdirectory(destination)
1491 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1493 call fugitive#reload_status()
1494 if s:buffer().commit() == ''
1495 if isdirectory(destination)
1496 return 'keepalt edit '.s:fnameescape(destination)
1498 return 'keepalt saveas! '.s:fnameescape(destination)
1501 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
1505 function! s:MoveComplete(A,L,P)
1507 return s:repo().superglob(a:A)
1509 let matches = split(glob(a:A.'*'),"\n")
1510 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1515 function! s:Remove(force)
1516 if s:buffer().commit() ==# ''
1518 elseif s:buffer().commit() ==# '0'
1519 let cmd = ['rm','--cached']
1521 let v:errmsg = 'fugitive: rm not supported here'
1522 return 'echoerr v:errmsg'
1525 let cmd += ['--force']
1527 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1529 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1530 return 'echoerr '.string(v:errmsg)
1532 call fugitive#reload_status()
1533 return 'bdelete'.(a:force ? '!' : '')
1537 augroup fugitive_remove
1539 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1540 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1541 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1548 augroup fugitive_blame
1550 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1551 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1552 autocmd Syntax fugitiveblame call s:BlameSyntax()
1553 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
1556 function! s:linechars(pattern)
1557 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1558 if exists('*synconcealed') && &conceallevel > 1
1559 for col in range(1, chars)
1560 let chars -= synconcealed(line('.'), col)[0]
1566 function! s:Blame(bang,line1,line2,count,args) abort
1568 if s:buffer().path() == ''
1569 call s:throw('file or blob required')
1571 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1572 call s:throw('unsupported option')
1574 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1575 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1576 if s:buffer().commit() =~# '\D\|..'
1577 let cmd += [s:buffer().commit()]
1579 let cmd += ['--contents', '-']
1581 let basecmd = escape(call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo()),'!')
1583 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1586 execute cd.'`=s:repo().tree()`'
1589 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1591 let error = resolve(tempname())
1592 let temp = error.'.fugitiveblame'
1594 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1596 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1603 call s:throw(join(readfile(error),"\n"))
1605 for winnr in range(winnr('$'),1,-1)
1606 call setwinvar(winnr, '&scrollbind', 0)
1607 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1608 execute winbufnr(winnr).'bdelete'
1611 let bufnr = bufnr('')
1612 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1614 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1617 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1619 setlocal scrollbind nowrap nofoldenable
1620 let top = line('w0') + &scrolloff
1621 let current = line('.')
1622 let s:temp_files[temp] = s:repo().dir()
1623 exe 'keepalt leftabove vsplit '.temp
1624 let b:fugitive_blamed_bufnr = bufnr
1625 let w:fugitive_leave = restore
1626 let b:fugitive_blame_arguments = join(a:args,' ')
1630 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
1631 if exists('+concealcursor')
1632 setlocal concealcursor=nc conceallevel=2
1634 if exists('+relativenumber')
1635 setlocal norelativenumber
1637 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1638 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1639 nnoremap <buffer> <silent> gq :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
1640 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
1641 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
1642 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1643 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1644 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1645 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1646 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1647 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1648 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1649 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1660 return 'echoerr v:errmsg'
1664 function! s:BlameCommit(cmd) abort
1665 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1666 if cmd =~# '^echoerr'
1669 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1670 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1672 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1675 if search('^diff .* b/\M'.escape(path,'\').'$','W')
1677 let head = line('.')
1678 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1679 let top = +matchstr(getline('.'),' +\zs\d\+')
1680 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
1681 if lnum >= top && lnum <= top + len
1682 let offset = lnum - top
1690 while offset > 0 && line('.') < line('$')
1692 if getline('.') =~# '^[ +]'
1696 return 'if foldlevel(".")|foldopen!|endif'
1705 function! s:BlameJump(suffix) abort
1706 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1707 if commit =~# '^0\+$'
1710 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1711 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1713 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1715 let args = b:fugitive_blame_arguments
1716 let offset = line('.') - line('w0')
1717 let bufnr = bufnr('%')
1718 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1720 exe winnr.'wincmd w'
1722 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
1727 execute 'Gblame '.args
1729 let delta = line('.') - line('w0') - offset
1731 execute 'normal! '.delta."\<C-E>"
1733 execute 'normal! '.(-delta)."\<C-Y>"
1739 function! s:BlameSyntax() abort
1740 let b:current_syntax = 'fugitiveblame'
1741 let conceal = has('conceal') ? ' conceal' : ''
1742 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
1743 syn match FugitiveblameBoundary "^\^"
1744 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1745 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1746 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1747 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1748 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1749 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
1750 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
1751 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
1752 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
1753 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
1754 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1755 hi def link FugitiveblameBoundary Keyword
1756 hi def link FugitiveblameHash Identifier
1757 hi def link FugitiveblameUncommitted Function
1758 hi def link FugitiveblameTime PreProc
1759 hi def link FugitiveblameLineNumber Number
1760 hi def link FugitiveblameOriginalFile String
1761 hi def link FugitiveblameOriginalLineNumber Float
1762 hi def link FugitiveblameShort FugitiveblameDelimiter
1763 hi def link FugitiveblameDelimiter Delimiter
1764 hi def link FugitiveblameNotCommittedYet Comment
1770 call s:command("-bar -bang -range -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1772 function! s:Browse(bang,line1,count,...) abort
1774 let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1776 let expanded = s:buffer().rev()
1778 let expanded = s:buffer().path('/')
1780 let expanded = s:buffer().expand(rev)
1782 let full = s:repo().translate(expanded)
1784 if full =~# '^fugitive://'
1785 let commit = matchstr(full,'://.*//\zs\w\+')
1786 let path = matchstr(full,'://.*//\w\+\zs/.*')
1788 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1792 let path = path[1:-1]
1793 elseif s:repo().bare()
1794 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1797 let path = full[strlen(s:repo().tree())+1:-1]
1798 if path =~# '^\.git/'
1800 elseif isdirectory(full)
1806 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1807 let body = readfile(s:repo().dir(path[5:-1]))[0]
1808 if body =~# '^\x\{40\}$'
1812 elseif body =~# '^ref: refs/'
1813 let path = '.git/' . matchstr(body,'ref: \zs.*')
1817 if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1818 let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1819 elseif path =~# '^\.git/refs/remotes/.'
1820 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1822 let remote = 'origin'
1823 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1824 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1825 let branch = s:sub(path,'^\.git/refs/\w+/','')
1827 if filereadable(s:repo().dir('refs/remotes/'.branch))
1828 let remote = matchstr(branch,'[^/]\+')
1829 let rev = rev[strlen(remote)+1:-1]
1832 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1835 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1836 if remote =~# '^\.\=$'
1837 let remote = 'origin'
1838 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1839 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1845 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1850 let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1852 let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count > 0 ? a:line1 : 0)
1856 call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1861 return 'echomsg '.string(url)
1863 return 'echomsg '.string(url).'|call fugitive#buffer().repo().git_chomp("web--browse",'.string(url).')'
1866 return 'echoerr v:errmsg'
1870 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1872 let domain_pattern = 'github\.com'
1873 for domain in exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
1874 let domain_pattern .= '\|' . escape(domain, '.')
1876 let repo = matchstr(a:url,'^\%(https\=://\|git://\|git@\)\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
1880 let root = 'https://' . s:sub(repo,':','/')
1881 if path =~# '^\.git/refs/heads/'
1882 let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1884 return root . '/commits/' . path[16:-1]
1886 return root . '/commits/' . branch
1888 elseif path =~# '^\.git/refs/.'
1889 return root . '/commits/' . matchstr(path,'[^/]\+$')
1890 elseif path =~# '.git/\%(config$\|hooks\>\)'
1891 return root . '/admin'
1892 elseif path =~# '^\.git\>'
1895 if a:rev =~# '^[[:alnum:]._-]\+:'
1896 let commit = matchstr(a:rev,'^[^:]*')
1897 elseif a:commit =~# '^\d\=$'
1898 let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
1899 let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
1904 let commit = a:commit
1907 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
1908 elseif a:type == 'blob'
1909 let url = root . '/blob/' . commit . '/' . path
1910 if a:line2 > 0 && a:line1 == a:line2
1911 let url .= '#L' . a:line1
1913 let url .= '#L' . a:line1 . '-' . a:line2
1915 elseif a:type == 'tag'
1916 let commit = matchstr(getline(3),'^tag \zs.*')
1917 let url = root . '/tree/' . commit
1919 let url = root . '/commit/' . commit
1924 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
1925 let output = a:repo.git_chomp('instaweb','-b','unknown')
1926 if output =~# 'http://'
1927 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
1931 if a:path =~# '^\.git/refs/.'
1932 return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
1933 elseif a:path =~# '^\.git\>'
1937 if a:commit =~# '^\x\{40\}$'
1938 if a:type ==# 'commit'
1939 let url .= ';a=commit'
1941 let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
1943 if a:type ==# 'blob'
1944 let tmp = tempname()
1945 silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
1946 let url .= ';h=' . readfile(tmp)[0]
1949 let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
1951 call s:throw('fugitive: cannot browse uncommitted file')
1954 let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
1957 let url .= ';f=' . a:path
1960 let url .= '#l' . a:1
1968 function! s:ReplaceCmd(cmd,...) abort
1969 let fn = expand('%:p')
1970 let tmp = tempname()
1975 let old_index = $GIT_INDEX_FILE
1976 let $GIT_INDEX_FILE = a:1
1978 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
1982 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
1983 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
1985 call system(' ('.prefix.a:cmd.' > '.tmp.') ')
1988 if exists('old_index')
1989 let $GIT_INDEX_FILE = old_index
1992 silent exe 'keepalt file '.tmp
1996 silent exe 'keepalt file '.s:fnameescape(fn)
1998 if fnamemodify(bufname('$'), ':p') ==# tmp
1999 silent execute 'bwipeout '.bufnr('$')
2001 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2005 function! s:BufReadIndex()
2006 if !exists('b:fugitive_display_format')
2007 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2009 let b:fugitive_display_format = b:fugitive_display_format % 2
2010 let b:fugitive_type = 'index'
2012 let b:git_dir = s:repo().dir()
2013 setlocal noro ma nomodeline
2014 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2017 let index = expand('%:p')
2019 if b:fugitive_display_format
2020 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2023 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2026 execute cd.'`=s:repo().tree()`'
2027 call s:ReplaceCmd(s:repo().git_command('status'),index)
2032 set foldtext=fugitive#foldtext() foldmethod=syntax foldlevel=1
2034 setlocal ro noma nomod noswapfile
2035 if &bufhidden ==# ''
2036 setlocal bufhidden=delete
2041 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2042 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2043 nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2044 xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2045 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2046 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2047 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2048 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2049 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2050 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2051 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2052 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2053 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2054 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2055 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2056 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2057 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2058 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2059 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2060 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2061 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2062 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2064 return 'echoerr v:errmsg'
2068 function! s:FileRead()
2070 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2071 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2072 let hash = repo.rev_parse(path)
2076 let type = repo.git_chomp('cat-file','-t',hash)
2078 " TODO: use count, if possible
2079 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2081 return 'echoerr v:errmsg'
2085 function! s:BufReadIndexFile()
2087 let b:fugitive_type = 'blob'
2088 let b:git_dir = s:repo().dir()
2090 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2092 if &bufhidden ==# ''
2093 setlocal bufhidden=delete
2097 catch /^fugitive: rev-parse/
2098 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2101 return 'echoerr v:errmsg'
2105 function! s:BufWriteIndexFile()
2106 let tmp = tempname()
2108 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2109 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2110 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2111 let sha1 = readfile(tmp)[0]
2112 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2114 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2116 let info = old_mode.' '.sha1.' '.stage."\t".path
2117 call writefile([info],tmp)
2119 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2121 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2123 if v:shell_error == 0
2125 silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2126 call fugitive#reload_status()
2129 return 'echoerr '.string('fugitive: '.error)
2136 function! s:BufReadObject()
2139 let b:git_dir = s:repo().dir()
2140 let hash = s:buffer().sha1()
2141 if !exists("b:fugitive_type")
2142 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2144 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2145 return "echoerr 'fugitive: unrecognized git type'"
2147 let firstline = getline('.')
2148 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2149 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2152 if b:fugitive_type !=# 'blob'
2156 let pos = getpos('.')
2161 if b:fugitive_type ==# 'tree'
2162 let b:fugitive_display_format = b:fugitive_display_format % 2
2163 if b:fugitive_display_format
2164 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2166 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2168 elseif b:fugitive_type ==# 'tag'
2169 let b:fugitive_display_format = b:fugitive_display_format % 2
2170 if b:fugitive_display_format
2171 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2173 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2175 elseif b:fugitive_type ==# 'commit'
2176 let b:fugitive_display_format = b:fugitive_display_format % 2
2177 if b:fugitive_display_format
2178 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2180 call s:ReplaceCmd(s:repo().git_command('show','--no-color','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
2181 call search('^parent ')
2182 if getline('.') ==# 'parent '
2185 silent s/\%(^parent\)\@<! /\rparent /ge
2187 if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2192 elseif b:fugitive_type ==# 'blob'
2193 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2196 call setpos('.',pos)
2197 setlocal ro noma nomod
2198 if &bufhidden ==# ''
2199 setlocal bufhidden=delete
2201 if b:fugitive_type !=# 'blob'
2203 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2204 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2212 return 'echoerr v:errmsg'
2216 augroup fugitive_files
2218 autocmd BufReadCmd index{,.lock}
2219 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2220 \ exe s:BufReadIndex() |
2221 \ elseif filereadable(expand('<amatch>')) |
2225 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2226 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2227 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2228 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2229 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2230 autocmd FileType git
2231 \ if exists('b:git_dir') |
2232 \ call s:JumpInit() |
2239 if !exists('s:temp_files')
2240 let s:temp_files = {}
2243 augroup fugitive_temp
2245 autocmd BufNewFile,BufReadPost *
2246 \ if has_key(s:temp_files,expand('<amatch>:p')) |
2247 \ let b:git_dir = s:temp_files[expand('<amatch>:p')] |
2248 \ let b:git_type = 'temp' |
2249 \ call s:Detect(expand('<amatch>:p')) |
2250 \ setlocal bufhidden=delete |
2251 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2258 function! s:JumpInit() abort
2259 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2262 nnoremap <buffer> <silent> <C-P> :<C-U>exe 'CtrlP '.fnameescape(<SID>repo().tree())<CR>
2264 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2265 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2266 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2267 nnoremap <buffer> <silent> - :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<CR>
2268 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2269 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2270 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2271 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2272 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2273 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2274 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2275 nnoremap <buffer> <silent> cp :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2279 function! s:GF(mode) abort
2281 let buffer = s:buffer()
2282 let myhash = buffer.sha1()
2283 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2284 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2287 if buffer.type('tree')
2288 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2289 if showtree && line('.') == 1
2291 elseif showtree && line('.') > 2
2292 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2293 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2294 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2297 elseif buffer.type('blob')
2298 let ref = expand("<cfile>")
2300 let sha1 = buffer.repo().rev_parse(ref)
2304 return s:Edit(a:mode,0,ref)
2310 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2311 let ref = matchstr(getline('.'),'\x\{40\}')
2312 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2313 return s:Edit(a:mode,0,file)
2315 elseif getline('.') =~# '^#\trenamed:.* -> '
2316 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2317 return s:Edit(a:mode,0,file)
2318 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2319 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2320 return s:Edit(a:mode,0,file)
2321 elseif getline('.') =~# '^#\t.'
2322 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2323 return s:Edit(a:mode,0,file)
2324 elseif getline('.') =~# ': needs merge$'
2325 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2326 return s:Edit(a:mode,0,file).'|Gdiff'
2328 elseif getline('.') ==# '# Not currently on any branch.'
2329 return s:Edit(a:mode,0,'HEAD')
2330 elseif getline('.') =~# '^# On branch '
2331 let file = 'refs/heads/'.getline('.')[12:]
2332 return s:Edit(a:mode,0,file)
2333 elseif getline('.') =~# "^# Your branch .*'"
2334 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2335 return s:Edit(a:mode,0,file)
2338 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2340 if getline('.') =~# '^ref: '
2341 let ref = strpart(getline('.'),5)
2343 elseif getline('.') =~# '^commit \x\{40\}\>'
2344 let ref = matchstr(getline('.'),'\x\{40\}')
2345 return s:Edit(a:mode,0,ref)
2347 elseif getline('.') =~# '^parent \x\{40\}\>'
2348 let ref = matchstr(getline('.'),'\x\{40\}')
2349 let line = line('.')
2351 while getline(line) =~# '^parent '
2355 return s:Edit(a:mode,0,ref)
2357 elseif getline('.') =~ '^tree \x\{40\}$'
2358 let ref = matchstr(getline('.'),'\x\{40\}')
2359 if s:repo().rev_parse(myhash.':') == ref
2360 let ref = myhash.':'
2362 return s:Edit(a:mode,0,ref)
2364 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2365 let ref = matchstr(getline('.'),'\x\{40\}')
2366 let type = matchstr(getline(line('.')+1),'type \zs.*')
2368 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2371 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2372 let ref = matchstr(getline('.'),'\x\{40\}')
2373 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2375 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2376 let ref = getline('.')[4:]
2378 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2379 let type = getline('.')[0]
2380 let lnum = line('.') - 1
2382 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2383 if getline(lnum) =~# '^[ '.type.']'
2388 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2389 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2390 let dcmd = '+'.offset.'|if foldlevel(".")|foldopen!|endif'
2393 elseif getline('.') =~# '^rename from '
2394 let ref = 'a/'.getline('.')[12:]
2395 elseif getline('.') =~# '^rename to '
2396 let ref = 'b/'.getline('.')[10:]
2398 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2399 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2400 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2403 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2404 let line = getline(line('.')-1)
2405 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2406 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2409 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2410 let ref = getline('.')
2416 let ref = s:sub(ref,'^a/','HEAD:')
2417 let ref = s:sub(ref,'^b/',':0:')
2419 let dref = s:sub(dref,'^a/','HEAD:')
2422 let ref = s:sub(ref,'^a/',myhash.'^:')
2423 let ref = s:sub(ref,'^b/',myhash.':')
2425 let dref = s:sub(dref,'^a/',myhash.'^:')
2429 if ref ==# '/dev/null'
2431 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2435 return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2437 return s:Edit(a:mode,0,ref)
2443 return 'echoerr v:errmsg'
2450 function! s:repo_head_ref() dict abort
2451 return readfile(self.dir('HEAD'))[0]
2454 call s:add_methods('repo',['head_ref'])
2456 function! fugitive#statusline(...)
2457 if !exists('b:git_dir')
2461 if s:buffer().commit() != ''
2462 let status .= ':' . s:buffer().commit()[0:7]
2464 let status .= '('.fugitive#head(7).')'
2465 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2466 return ',GIT'.status
2468 return '[Git'.status.']'
2472 function! fugitive#head(...)
2473 if !exists('b:git_dir')
2477 return s:repo().head(a:0 ? a:1 : 0)
2483 function! fugitive#foldtext() abort
2484 if &foldmethod !=# 'syntax'
2486 elseif getline(v:foldstart) =~# '^diff '
2487 let [add, remove] = [-1, -1]
2489 for lnum in range(v:foldstart, v:foldend)
2490 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2491 let filename = getline(lnum)[6:-1]
2493 if getline(lnum) =~# '^+'
2495 elseif getline(lnum) =~# '^-'
2497 elseif getline(lnum) =~# '^Binary '
2502 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2505 let filename = getline(v:foldstart)[5:-1]
2508 return 'Binary: '.filename
2510 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2512 elseif getline(v:foldstart) =~# '^# .*:$'
2513 let lines = getline(v:foldstart, v:foldend)
2514 call filter(lines, 'v:val =~# "^#\t"')
2515 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2516 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2517 return getline(v:foldstart).' '.join(lines, ', ')
2522 augroup fugitive_foldtext
2524 autocmd User Fugitive
2525 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2526 \ set foldtext=fugitive#foldtext() |