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:winshell() abort
30 return exists('+shellslash') && !&shellslash
33 function! s:shellesc(arg) abort
34 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
37 return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
39 return shellescape(a:arg)
43 function! s:fnameescape(file) abort
44 if exists('*fnameescape')
45 return fnameescape(a:file)
47 return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
51 function! s:throw(string) abort
52 let v:errmsg = 'fugitive: '.a:string
56 function! s:warn(str) abort
60 let v:warningmsg = a:str
63 function! s:shellslash(path) abort
65 return s:gsub(a:path,'\\','/')
71 let s:git_versions = {}
73 function! fugitive#git_version(...) abort
74 if !has_key(s:git_versions, g:fugitive_git_executable)
75 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\n")
77 return s:git_versions[g:fugitive_git_executable]
80 function! s:recall() abort
81 let rev = s:sub(s:buffer().rev(), '^/', '')
83 return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
84 elseif s:buffer().type('tree')
85 let file = matchstr(getline('.'), '\t\zs.*')
86 if empty(file) && line('.') > 2
87 let file = s:sub(getline('.'), '/$', '')
89 if !empty(file) && rev !~# ':$'
90 return rev . '/' . file
98 function! s:add_methods(namespace, method_names) abort
99 for name in a:method_names
100 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
105 function! s:command(definition) abort
106 let s:commands += [a:definition]
109 function! s:define_commands() abort
110 for command in s:commands
111 exe 'command! -buffer '.command
115 augroup fugitive_utility
117 autocmd User Fugitive call s:define_commands()
120 let s:abstract_prototype = {}
122 " Section: Initialization
124 function! fugitive#is_git_dir(path) abort
125 let path = s:sub(a:path, '[\/]$', '') . '/'
126 return isdirectory(path.'objects') && isdirectory(path.'refs') && getfsize(path.'HEAD') > 10
129 function! fugitive#extract_git_dir(path) abort
130 if s:shellslash(a:path) =~# '^fugitive://.*//'
131 return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
133 let root = s:shellslash(simplify(fnamemodify(a:path, ':p:s?[\/]$??')))
135 while root !=# previous
136 if root =~# '\v^//%([^/]+/?)?$'
137 " This is for accessing network shares from Cygwin Vim. There won't be
138 " any git directory called //.git or //serverName/.git so let's avoid
139 " checking for them since such checks are extremely slow.
142 if index(split($GIT_CEILING_DIRECTORIES, ':'), root) >= 0
145 if root ==# $GIT_WORK_TREE && fugitive#is_git_dir($GIT_DIR)
148 let dir = s:sub(root, '[\/]$', '') . '/.git'
149 let type = getftype(dir)
150 if type ==# 'dir' && fugitive#is_git_dir(dir)
152 elseif type ==# 'link' && fugitive#is_git_dir(dir)
154 elseif type !=# '' && filereadable(dir)
155 let line = get(readfile(dir, '', 1), 0, '')
156 if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
157 return simplify(root.'/'.line[8:-1])
158 elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
161 elseif fugitive#is_git_dir(root)
165 let root = fnamemodify(root, ':h')
170 function! fugitive#detect(path) abort
171 if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
174 if !exists('b:git_dir')
175 let dir = fugitive#extract_git_dir(a:path)
180 if exists('b:git_dir')
181 silent doautocmd User FugitiveBoot
182 cnoremap <buffer> <expr> <C-R><C-G> fnameescape(<SID>recall())
183 nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
184 let buffer = fugitive#buffer()
185 if expand('%:p') =~# '//'
186 call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
188 if stridx(buffer.getvar('&tags'), escape(b:git_dir.'/tags', ', ')) == -1
189 call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
191 call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
194 silent doautocmd User Fugitive
200 autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('<amatch>:p'))
201 autocmd FileType netrw call fugitive#detect(expand('%:p'))
202 autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTreeRoot.path.str())
203 autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
204 autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
205 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
208 " Section: Repository
210 let s:repo_prototype = {}
213 function! s:repo(...) abort
214 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
216 if has_key(s:repos, dir)
217 let repo = get(s:repos, dir)
219 let repo = {'git_dir': dir}
220 let s:repos[dir] = repo
222 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
224 call s:throw('not a git repository: '.expand('%:p'))
227 function! fugitive#repo(...) abort
228 return call('s:repo', a:000)
231 function! s:repo_dir(...) dict abort
232 return join([self.git_dir]+a:000,'/')
235 function! s:repo_configured_tree() dict abort
236 if !has_key(self,'_tree')
238 if filereadable(self.dir('config'))
239 let config = readfile(self.dir('config'),'',10)
240 call filter(config,'v:val =~# "^\\s*worktree *="')
242 let self._tree = matchstr(config[0], '= *\zs.*')
246 if self._tree =~# '^\.'
247 return simplify(self.dir(self._tree))
253 function! s:repo_tree(...) dict abort
254 if self.dir() =~# '/\.git$'
255 let dir = self.dir()[0:-6]
257 let dir = self.configured_tree()
260 call s:throw('no work tree')
262 return join([dir]+a:000,'/')
266 function! s:repo_bare() dict abort
267 if self.dir() =~# '/\.git$'
270 return self.configured_tree() ==# ''
274 function! s:repo_translate(spec) dict abort
275 if a:spec ==# '.' || a:spec ==# '/.'
276 return self.bare() ? self.dir() : self.tree()
277 elseif a:spec =~# '^/\=\.git$' && self.bare()
279 elseif a:spec =~# '^/\=\.git/'
280 return self.dir(s:sub(a:spec, '^/=\.git/', ''))
281 elseif a:spec =~# '^/'
282 return self.tree().a:spec
283 elseif a:spec =~# '^:[0-3]:'
284 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
285 elseif a:spec ==# ':'
286 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
287 return fnamemodify($GIT_INDEX_FILE,':p')
289 return self.dir('index')
291 elseif a:spec =~# '^:/'
292 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
293 return 'fugitive://'.self.dir().'//'.ref
294 elseif a:spec =~# '^:'
295 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
296 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
297 return self.dir(a:spec)
298 elseif filereadable(self.dir('refs/'.a:spec))
299 return self.dir('refs/'.a:spec)
300 elseif filereadable(self.dir('refs/tags/'.a:spec))
301 return self.dir('refs/tags/'.a:spec)
302 elseif filereadable(self.dir('refs/heads/'.a:spec))
303 return self.dir('refs/heads/'.a:spec)
304 elseif filereadable(self.dir('refs/remotes/'.a:spec))
305 return self.dir('refs/remotes/'.a:spec)
306 elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
307 return self.dir('refs/remotes/'.a:spec,'/HEAD')
310 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
311 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
312 return 'fugitive://'.self.dir().'//'.ref.path
314 return self.tree(a:spec)
319 function! s:repo_head(...) dict abort
320 let head = s:repo().head_ref()
323 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
324 elseif head =~# '^\x\{40\}$'
325 " truncate hash to a:1 characters if we're in detached head mode
326 let len = a:0 ? a:1 : 0
327 let branch = len ? head[0:len-1] : ''
335 call s:add_methods('repo',['dir','configured_tree','tree','bare','translate','head'])
337 function! s:repo_git_command(...) dict abort
338 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
339 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
342 function! s:repo_git_chomp(...) dict abort
343 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
346 function! s:repo_git_chomp_in_tree(...) dict abort
347 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
350 execute cd.'`=s:repo().tree()`'
351 return call(s:repo().git_chomp, a:000, s:repo())
357 function! s:repo_rev_parse(rev) dict abort
358 let hash = self.git_chomp('rev-parse','--verify',a:rev)
359 if hash =~ '\<\x\{40\}$'
360 return matchstr(hash,'\<\x\{40\}$')
362 call s:throw('rev-parse '.a:rev.': '.hash)
365 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
367 function! s:repo_dirglob(base) dict abort
368 let base = s:sub(a:base,'^/','')
369 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
370 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
374 function! s:repo_superglob(base) dict abort
375 if a:base =~# '^/' || a:base !~# ':'
378 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
379 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
380 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
384 let base = s:sub(a:base,'^/','')
385 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
386 call map(matches,'s:shellslash(v:val)')
387 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
388 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
389 let results += matches
393 elseif a:base =~# '^:'
394 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
395 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
396 if a:base !~# '^:[0-3]\%(:\|$\)'
397 call filter(entries,'v:val[1] == "0"')
398 call map(entries,'v:val[2:-1]')
400 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
404 let tree = matchstr(a:base,'.*[:/]')
405 let entries = split(self.git_chomp('ls-tree',tree),"\n")
406 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
407 call map(entries,'tree.s:sub(v:val,".*\t","")')
408 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
412 call s:add_methods('repo',['dirglob','superglob'])
414 function! s:repo_config(conf) dict abort
415 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
418 function! s:repo_user() dict abort
419 let username = s:repo().config('user.name')
420 let useremail = s:repo().config('user.email')
421 return username.' <'.useremail.'>'
424 function! s:repo_aliases() dict abort
425 if !has_key(self,'_aliases')
426 let self._aliases = {}
427 for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
428 let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
434 call s:add_methods('repo',['config', 'user', 'aliases'])
436 function! s:repo_keywordprg() dict abort
437 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
438 if has('gui_running') && !has('win32')
439 return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
441 return g:fugitive_git_executable . args . ' show'
445 call s:add_methods('repo',['keywordprg'])
449 let s:buffer_prototype = {}
451 function! s:buffer(...) abort
452 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
453 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
454 if buffer.getvar('git_dir') !=# ''
457 call s:throw('not a git repository: '.expand('%:p'))
460 function! fugitive#buffer(...) abort
461 return s:buffer(a:0 ? a:1 : '%')
464 function! s:buffer_getvar(var) dict abort
465 return getbufvar(self['#'],a:var)
468 function! s:buffer_setvar(var,value) dict abort
469 return setbufvar(self['#'],a:var,a:value)
472 function! s:buffer_getline(lnum) dict abort
473 return get(getbufline(self['#'], a:lnum), 0, '')
476 function! s:buffer_repo() dict abort
477 return s:repo(self.getvar('git_dir'))
480 function! s:buffer_type(...) dict abort
481 if self.getvar('fugitive_type') != ''
482 let type = self.getvar('fugitive_type')
483 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
485 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
487 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
489 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
491 elseif isdirectory(self.spec())
492 let type = 'directory'
493 elseif self.spec() == ''
499 return !empty(filter(copy(a:000),'v:val ==# type'))
507 function! s:buffer_spec() dict abort
508 let bufname = bufname(self['#'])
510 for i in split(bufname,'[^:]\zs\\')
511 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
513 return s:shellslash(fnamemodify(retval,':p'))
518 function! s:buffer_spec() dict abort
519 let bufname = bufname(self['#'])
520 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
525 function! s:buffer_name() dict abort
529 function! s:buffer_commit() dict abort
530 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
533 function! s:buffer_path(...) dict abort
534 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
536 let rev = s:sub(rev,'\w*','')
537 elseif self.spec()[0 : len(self.repo().dir())] ==# self.repo().dir() . '/'
538 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
539 elseif !self.repo().bare() && self.spec()[0 : len(self.repo().tree())] ==# self.repo().tree() . '/'
540 let rev = self.spec()[strlen(self.repo().tree()) : -1]
542 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
545 function! s:buffer_rev() dict abort
546 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
548 return ':'.rev[0].':'.rev[2:-1]
550 return s:sub(rev,'/',':')
551 elseif self.spec() =~ '\.git/index$'
553 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
554 return self.spec()[strlen(self.repo().dir())+1 : -1]
556 return self.path('/')
560 function! s:buffer_sha1() dict abort
561 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
562 return self.repo().rev_parse(self.rev())
568 function! s:buffer_expand(rev) dict abort
569 if a:rev =~# '^:[0-3]$'
570 let file = a:rev.self.path(':')
571 elseif a:rev =~# '^[-:]/$'
572 let file = '/'.self.path()
573 elseif a:rev =~# '^-'
574 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
575 elseif a:rev =~# '^@{'
576 let file = 'HEAD'.a:rev.self.path(':')
577 elseif a:rev =~# '^[~^]'
578 let commit = s:sub(self.commit(),'^\d=$','HEAD')
579 let file = commit.a:rev.self.path(':')
583 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
586 function! s:buffer_containing_commit() dict abort
587 if self.commit() =~# '^\d$'
589 elseif self.commit() =~# '.'
596 function! s:buffer_up(...) dict abort
598 let c = a:0 ? a:1 : 1
604 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
606 elseif rev =~# '^/\|:.*/'
607 let rev = s:sub(rev, '.*\zs/.*', '')
609 let rev = matchstr(rev, '^[^:]*:')
620 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
624 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
626 function! s:ExecuteInTree(cmd) abort
627 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
630 execute cd.'`=s:repo().tree()`'
637 function! s:Git(bang, args) abort
639 return s:Edit('edit', 1, a:args)
641 let git = g:fugitive_git_executable
642 if has('gui_running') && !has('win32')
643 let git .= ' --no-pager'
645 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
646 call s:ExecuteInTree('!'.git.' '.args)
647 call fugitive#reload_status()
648 return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
651 function! s:GitComplete(A,L,P) abort
652 if !exists('s:exec_path')
653 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
655 let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
656 if a:L =~ ' [[:alnum:]-]\+ '
657 return s:repo().superglob(a:A)
659 return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
665 function! s:DirComplete(A,L,P) abort
666 let matches = s:repo().dirglob(a:A)
670 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>)`")
671 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>)`")
675 call s:command("-bar Gstatus :execute s:Status()")
676 augroup fugitive_status
679 autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
683 function! s:Status() abort
687 setlocal foldmethod=syntax foldlevel=1
688 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
690 return 'echoerr v:errmsg'
695 function! fugitive#reload_status() abort
696 if exists('s:reloading_status')
700 let s:reloading_status = 1
701 let mytab = tabpagenr()
702 for tab in [mytab] + range(1,tabpagenr('$'))
703 for winnr in range(1,tabpagewinnr(tab,'$'))
704 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
705 execute 'tabnext '.tab
707 execute winnr.'wincmd w'
712 call s:BufReadIndex()
715 if exists('restorewinnr')
718 execute 'tabnext '.mytab
724 unlet! s:reloading_status
728 function! s:stage_info(lnum) abort
729 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
731 if has('multi_byte_encoding')
732 let colon = '\%(:\|\%uff1a\)'
736 while lnum && getline(lnum) !~# colon.'$'
741 elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
742 return [matchstr(filename, colon.' *\zs.*'), 'staged']
743 elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
744 return [filename, 'untracked']
745 elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
746 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
747 elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
748 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
750 return ['', 'unknown']
754 function! s:StageNext(count) abort
755 for i in range(a:count)
756 call search('^#\t.*','W')
761 function! s:StagePrevious(count) abort
762 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
763 return 'CtrlP '.fnameescape(s:repo().tree())
765 for i in range(a:count)
766 call search('^#\t.*','Wbe')
772 function! s:StageReloadSeek(target,lnum1,lnum2) abort
774 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
775 if f !=# '' | let jump = f | endif
776 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
777 if f !=# '' | let jump = f | endif
781 call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
784 function! s:StageUndo() abort
785 let [filename, section] = s:stage_info(line('.'))
790 let hash = repo.git_chomp('hash-object', '-w', filename)
792 if section ==# 'untracked'
793 call delete(s:repo().tree(filename))
794 elseif section ==# 'unstaged'
795 call repo.get_chomp('checkout', '--', filename)
797 call repo.get_chomp('checkout', 'HEAD', '--', filename)
799 call s:StageReloadSeek(filename, line('.'), line('.'))
801 return 'checktime|redraw|echomsg ' .
802 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
806 function! s:StageDiff(diff) abort
807 let [filename, section] = s:stage_info(line('.'))
808 if filename ==# '' && section ==# 'staged'
809 return 'Git! diff --no-ext-diff --cached'
810 elseif filename ==# ''
811 return 'Git! diff --no-ext-diff'
812 elseif filename =~# ' -> '
813 let [old, new] = split(filename,' -> ')
814 execute 'Gedit '.s:fnameescape(':0:'.new)
815 return a:diff.' HEAD:'.s:fnameescape(old)
816 elseif section ==# 'staged'
817 execute 'Gedit '.s:fnameescape(':0:'.filename)
820 execute 'Gedit '.s:fnameescape('/'.filename)
825 function! s:StageDiffEdit() abort
826 let [filename, section] = s:stage_info(line('.'))
827 let arg = (filename ==# '' ? '.' : filename)
828 if section ==# 'staged'
829 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
830 elseif section ==# 'untracked'
832 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
836 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
837 call search('^# .*:$','W')
840 call s:StageReloadSeek(arg,line('.'),line('.'))
844 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
848 function! s:StageToggle(lnum1,lnum2) abort
849 if a:lnum1 == 1 && a:lnum2 == 1
850 return 'Gedit /.git|call search("^index$", "wc")'
854 for lnum in range(a:lnum1,a:lnum2)
855 let [filename, section] = s:stage_info(lnum)
857 if getline('.') =~# '^# .*:$'
858 if section ==# 'staged'
859 call repo.git_chomp_in_tree('reset','-q')
862 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
863 call search('^# .*:$','W')
866 elseif section ==# 'unstaged'
867 call repo.git_chomp_in_tree('add','-u')
870 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
871 call search('^# .*:$','W')
875 call repo.git_chomp_in_tree('add','.')
878 call search('^# .*:$','W')
885 if !exists('first_filename')
886 let first_filename = filename
889 if filename =~ ' -> '
890 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
891 let filename = cmd[-1]
892 elseif section ==# 'staged'
893 let cmd = ['reset','-q','--',filename]
894 elseif getline(lnum) =~# '^#\tdeleted:'
895 let cmd = ['rm','--',filename]
896 elseif getline(lnum) =~# '^#\tmodified:'
897 let cmd = ['add','--',filename]
899 let cmd = ['add','-A','--',filename]
901 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
903 if exists('first_filename')
904 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
906 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
908 return 'echoerr v:errmsg'
913 function! s:StagePatch(lnum1,lnum2) abort
917 for lnum in range(a:lnum1,a:lnum2)
918 let [filename, section] = s:stage_info(lnum)
919 if getline('.') =~# '^# .*:$' && section ==# 'staged'
920 return 'Git reset --patch'
921 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
922 return 'Git add --patch'
923 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
924 return 'Git add -N .'
925 elseif filename ==# ''
928 if !exists('first_filename')
929 let first_filename = filename
932 if filename =~ ' -> '
933 let reset += [split(filename,' -> ')[1]]
934 elseif section ==# 'staged'
935 let reset += [filename]
936 elseif getline(lnum) !~# '^#\tdeleted:'
937 let add += [filename]
942 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
945 execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
947 if exists('first_filename')
951 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
954 return 'echoerr v:errmsg'
961 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
963 function! s:Commit(args) abort
964 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
966 let msgfile = s:repo().dir('COMMIT_EDITMSG')
967 let outfile = tempname()
968 let errorfile = tempname()
971 execute cd.s:fnameescape(s:repo().tree())
974 let old_editor = $GIT_EDITOR
975 let $GIT_EDITOR = 'false'
977 let command = 'env GIT_EDITOR=false '
979 let command .= s:repo().git_command('commit').' '.a:args
981 noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
982 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
983 noautocmd execute '!'.command.' 2> '.errorfile
985 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
990 if !has('gui_running')
994 if filereadable(outfile)
995 for line in readfile(outfile)
1001 let errors = readfile(errorfile)
1002 let error = get(errors,-2,get(errors,-1,'!'))
1003 if error =~# 'false''\=\.$'
1005 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|patch|--signoff)%($| )','')
1006 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1007 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1008 let args = '-F '.s:shellesc(msgfile).' '.args
1009 if args !~# '\%(^\| \)--cleanup\>'
1010 let args = '--cleanup=strip '.args
1012 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1013 execute 'keepalt edit '.s:fnameescape(msgfile)
1014 elseif s:buffer().type() ==# 'index'
1015 execute 'keepalt edit '.s:fnameescape(msgfile)
1016 execute (search('^#','n')+1).'wincmd+'
1017 setlocal nopreviewwindow
1019 execute 'keepalt split '.s:fnameescape(msgfile)
1021 let b:fugitive_commit_arguments = args
1022 setlocal bufhidden=wipe filetype=gitcommit
1024 elseif error ==# '!'
1031 return 'echoerr v:errmsg'
1033 if exists('old_editor')
1034 let $GIT_EDITOR = old_editor
1036 call delete(outfile)
1037 call delete(errorfile)
1038 call fugitive#reload_status()
1042 function! s:CommitComplete(A,L,P) abort
1043 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1044 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']
1045 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1047 return s:repo().superglob(a:A)
1051 function! s:FinishCommit() abort
1052 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1054 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1055 return s:Commit(args)
1060 augroup fugitive_commit
1062 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1065 " Section: Ggrep, Glog
1067 if !exists('g:fugitive_summary_format')
1068 let g:fugitive_summary_format = '%s'
1071 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1072 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1073 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<count>,<f-args>)")
1074 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<count>,<f-args>)")
1076 function! s:Grep(cmd,bang,arg) abort
1077 let grepprg = &grepprg
1078 let grepformat = &grepformat
1079 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1082 execute cd.'`=s:repo().tree()`'
1083 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1084 let &grepformat = '%f:%l:%m'
1085 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1086 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1088 if bufname(entry.bufnr) =~ ':'
1089 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1092 elseif a:arg =~# '\%(^\| \)--cached\>'
1093 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1098 if a:cmd =~# '^l' && exists('changed')
1099 call setloclist(0, list, 'r')
1100 elseif exists('changed')
1101 call setqflist(list, 'r')
1103 if !a:bang && !empty(list)
1104 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1106 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1109 let &grepprg = grepprg
1110 let &grepformat = grepformat
1115 function! s:Log(cmd, count, ...) abort
1116 let path = s:buffer().path('/')
1117 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1120 let cmd = ['--no-pager', 'log', '--no-color']
1121 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.':'.(a:count ? a:count : '').'::'.g:fugitive_summary_format]
1122 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1123 if s:buffer().commit() =~# '\x\{40\}'
1124 let cmd += [s:buffer().commit()]
1125 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1126 let cmd += [s:buffer().path()[5:-1]]
1129 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1131 let cmd += ['--',path[1:-1]]
1133 let grepformat = &grepformat
1134 let grepprg = &grepprg
1135 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1138 execute cd.'`=s:repo().tree()`'
1139 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1140 let &grepformat = '%f:%l::%m,%f:::%m'
1143 let &grepformat = grepformat
1144 let &grepprg = grepprg
1149 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1151 function! s:Edit(cmd,bang,...) abort
1152 let buffer = s:buffer()
1154 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1157 let mywinnr = winnr()
1158 for winnr in range(winnr('$'),1,-1)
1159 if winnr != mywinnr && getwinvar(winnr,'&diff')
1160 execute winnr.'wincmd w'
1170 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1171 let args = join(arglist, ' ')
1173 let git = buffer.repo().git_command()
1174 let last = line('$')
1175 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1177 silent execute '1,'.last.'delete_'
1179 call fugitive#reload_status()
1181 return 'redraw|echo '.string(':!'.git.' '.args)
1183 let temp = resolve(tempname())
1184 let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1185 silent execute a:cmd.' '.temp
1186 if a:cmd =~# 'pedit'
1189 let echo = s:Edit('read',1,args)
1191 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1192 if getline(1) !~# '^diff '
1193 setlocal readonly nomodifiable
1195 if a:cmd =~# 'pedit'
1206 let file = buffer.expand(join(a:000, ' '))
1207 elseif expand('%') ==# ''
1209 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1210 let file = buffer.path(':')
1212 let file = buffer.path('/')
1215 let file = buffer.repo().translate(file)
1217 return 'echoerr v:errmsg'
1220 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1222 return a:cmd.' '.s:fnameescape(file)
1226 function! s:EditComplete(A,L,P) abort
1227 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1230 function! s:EditRunComplete(A,L,P) abort
1232 return s:GitComplete(a:A,a:L,a:P)
1234 return s:repo().superglob(a:A)
1238 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1239 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1240 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1241 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1242 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1243 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1244 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1246 " Section: Gwrite, Gwq
1248 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1249 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1250 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1252 function! s:Write(force,...) abort
1253 if exists('b:fugitive_commit_arguments')
1254 return 'write|bdelete'
1255 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1257 elseif s:buffer().type() == 'index'
1259 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1260 let filename = getline(4)[6:-1]
1263 setlocal buftype=nowrite
1264 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1265 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1267 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1270 let v:errmsg = split(err,"\n")[0]
1271 return 'echoerr v:errmsg'
1275 return 'Gedit '.fnameescape(filename)
1278 let mytab = tabpagenr()
1279 let mybufnr = bufnr('')
1280 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1281 if path =~# '^:\d\>'
1282 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1284 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1285 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) !=# ''
1286 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1287 return 'echoerr v:errmsg'
1289 let file = s:repo().translate(path)
1291 for nr in range(1,bufnr('$'))
1292 if fnamemodify(bufname(nr),':p') ==# file
1297 if treebufnr > 0 && treebufnr != bufnr('')
1298 let temp = tempname()
1299 silent execute '%write '.temp
1300 for tab in [mytab] + range(1,tabpagenr('$'))
1301 for winnr in range(1,tabpagewinnr(tab,'$'))
1302 if tabpagebuflist(tab)[winnr-1] == treebufnr
1303 execute 'tabnext '.tab
1305 execute winnr.'wincmd w'
1306 let restorewinnr = 1
1309 let lnum = line('.')
1310 let last = line('$')
1311 silent execute '$read '.temp
1312 silent execute '1,'.last.'delete_'
1317 if exists('restorewinnr')
1320 execute 'tabnext '.mytab
1326 call writefile(readfile(temp,'b'),file,'b')
1329 execute 'write! '.s:fnameescape(s:repo().translate(path))
1333 let error = s:repo().git_chomp_in_tree('add', '--force', file)
1335 let error = s:repo().git_chomp_in_tree('add', file)
1338 let v:errmsg = 'fugitive: '.error
1339 return 'echoerr v:errmsg'
1341 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1345 let one = s:repo().translate(':1:'.path)
1346 let two = s:repo().translate(':2:'.path)
1347 let three = s:repo().translate(':3:'.path)
1348 for nr in range(1,bufnr('$'))
1349 let name = fnamemodify(bufname(nr), ':p')
1350 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1351 execute nr.'bdelete'
1356 let zero = s:repo().translate(':0:'.path)
1357 for tab in range(1,tabpagenr('$'))
1358 for winnr in range(1,tabpagewinnr(tab,'$'))
1359 let bufnr = tabpagebuflist(tab)[winnr-1]
1360 let bufname = fnamemodify(bufname(bufnr), ':p')
1361 if bufname ==# zero && bufnr != mybufnr
1362 execute 'tabnext '.tab
1364 execute winnr.'wincmd w'
1365 let restorewinnr = 1
1368 let lnum = line('.')
1369 let last = line('$')
1370 silent execute '$read '.s:fnameescape(file)
1371 silent execute '1,'.last.'delete_'
1376 if exists('restorewinnr')
1379 execute 'tabnext '.mytab
1385 call fugitive#reload_status()
1389 function! s:Wq(force,...) abort
1390 let bang = a:force ? '!' : ''
1391 if exists('b:fugitive_commit_arguments')
1394 let result = call(s:function('s:Write'),[a:force]+a:000)
1395 if result =~# '^\%(write\|wq\|echoerr\)'
1396 return s:sub(result,'^write','wq')
1398 return result.'|quit'.bang
1404 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('keepalt ',<f-args>)")
1405 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1406 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('',<f-args>)")
1408 augroup fugitive_diff
1410 autocmd BufWinLeave *
1411 \ if getwinvar(bufwinnr(+expand('<abuf>')), '&diff') &&
1412 \ s:diff_window_count() == 2 &&
1413 \ !empty(getbufvar(+expand('<abuf>'), 'git_dir')) |
1414 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1416 autocmd BufWinEnter *
1417 \ if getwinvar(bufwinnr(+expand('<abuf>')), '&diff') &&
1418 \ s:diff_window_count() == 1 &&
1419 \ !empty(getbufvar(+expand('<abuf>'), 'git_dir')) |
1420 \ call s:diffoff() |
1424 function! s:diff_horizontal(count) abort
1425 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1426 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1427 return 'keepalt vert '
1428 elseif &diffopt =~# 'vertical'
1430 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1433 return 'keepalt vert '
1437 function! s:diff_window_count() abort
1439 for nr in range(1,winnr('$'))
1440 let c += getwinvar(nr,'&diff')
1445 function! s:diff_restore() abort
1446 let restore = 'setlocal nodiff noscrollbind'
1447 \ . ' scrollopt=' . &l:scrollopt
1448 \ . (&l:wrap ? ' wrap' : ' nowrap')
1449 \ . ' foldlevel=999'
1450 \ . ' foldmethod=' . &l:foldmethod
1451 \ . ' foldcolumn=' . &l:foldcolumn
1452 \ . ' foldlevel=' . &l:foldlevel
1453 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1454 if has('cursorbind')
1455 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1460 function! s:diffthis() abort
1462 let w:fugitive_diff_restore = s:diff_restore()
1467 function! s:diffoff() abort
1468 if exists('w:fugitive_diff_restore')
1469 execute w:fugitive_diff_restore
1470 unlet w:fugitive_diff_restore
1476 function! s:diffoff_all(dir) abort
1477 for nr in range(1,winnr('$'))
1478 if getwinvar(nr,'&diff')
1480 execute nr.'wincmd w'
1481 let restorewinnr = 1
1483 if exists('b:git_dir') && b:git_dir ==# a:dir
1490 function! s:buffer_compare_age(commit) dict abort
1491 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1492 let my_score = get(scores,':'.self.commit(),0)
1493 let their_score = get(scores,':'.a:commit,0)
1494 if my_score || their_score
1495 return my_score < their_score ? -1 : my_score != their_score
1496 elseif self.commit() ==# a:commit
1499 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1500 if base ==# self.commit()
1502 elseif base ==# a:commit
1505 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1506 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1507 return my_time < their_time ? -1 : my_time != their_time
1510 call s:add_methods('buffer',['compare_age'])
1512 function! s:Diff(vert,...) abort
1513 let vert = empty(a:vert) ? s:diff_horizontal(2) : a:vert
1514 if exists(':DiffGitCached')
1515 return 'DiffGitCached'
1516 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1517 let vert = empty(a:vert) ? s:diff_horizontal(3) : a:vert
1519 execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1520 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1523 execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1524 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1530 let arg = join(a:000, ' ')
1534 let file = s:buffer().path('/')
1536 let file = s:buffer().path(':0:')
1537 elseif arg =~# '^:/.'
1539 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1541 return 'echoerr v:errmsg'
1544 let file = s:buffer().expand(arg)
1546 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1547 let file = file.s:buffer().path(':')
1550 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1553 let spec = s:repo().translate(file)
1554 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1555 let restore = s:diff_restore()
1556 if exists('+cursorbind')
1559 let w:fugitive_diff_restore = restore
1560 if s:buffer().compare_age(commit) < 0
1561 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1563 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1565 let w:fugitive_diff_restore = restore
1567 if getwinvar('#', '&diff')
1569 call feedkeys("\<C-W>p", 'n')
1573 return 'echoerr v:errmsg'
1577 " Section: Gmove, Gremove
1579 function! s:Move(force,destination) abort
1580 if a:destination =~# '^/'
1581 let destination = a:destination[1:-1]
1583 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1584 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1585 let destination = destination[strlen(s:repo().tree('')):-1]
1588 if isdirectory(s:buffer().spec())
1589 " Work around Vim parser idiosyncrasy
1590 let discarded = s:buffer().setvar('&swapfile',0)
1592 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1594 let v:errmsg = 'fugitive: '.message
1595 return 'echoerr v:errmsg'
1597 let destination = s:repo().tree(destination)
1598 if isdirectory(destination)
1599 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1601 call fugitive#reload_status()
1602 if s:buffer().commit() == ''
1603 if isdirectory(destination)
1604 return 'keepalt edit '.s:fnameescape(destination)
1606 return 'keepalt saveas! '.s:fnameescape(destination)
1609 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1613 function! s:MoveComplete(A,L,P) abort
1615 return s:repo().superglob(a:A)
1617 let matches = split(glob(a:A.'*'),"\n")
1618 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1623 function! s:Remove(force) abort
1624 if s:buffer().commit() ==# ''
1626 elseif s:buffer().commit() ==# '0'
1627 let cmd = ['rm','--cached']
1629 let v:errmsg = 'fugitive: rm not supported here'
1630 return 'echoerr v:errmsg'
1633 let cmd += ['--force']
1635 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1637 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1638 return 'echoerr '.string(v:errmsg)
1640 call fugitive#reload_status()
1641 return 'bdelete'.(a:force ? '!' : '')
1645 augroup fugitive_remove
1647 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1648 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1649 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1655 augroup fugitive_blame
1657 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1658 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1659 autocmd Syntax fugitiveblame call s:BlameSyntax()
1660 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
1663 function! s:linechars(pattern) abort
1664 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1665 if exists('*synconcealed') && &conceallevel > 1
1666 for col in range(1, chars)
1667 let chars -= synconcealed(line('.'), col)[0]
1673 function! s:Blame(bang,line1,line2,count,args) abort
1675 if s:buffer().path() == ''
1676 call s:throw('file or blob required')
1678 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1679 call s:throw('unsupported option')
1681 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1682 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1683 if s:buffer().commit() =~# '\D\|..'
1684 let cmd += [s:buffer().commit()]
1686 let cmd += ['--contents', '-']
1688 let cmd += ['--', s:buffer().path()]
1689 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1691 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1694 execute cd.'`=s:repo().tree()`'
1697 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1699 let error = resolve(tempname())
1700 let temp = error.'.fugitiveblame'
1702 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1704 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1711 call s:throw(join(readfile(error),"\n"))
1713 for winnr in range(winnr('$'),1,-1)
1714 call setwinvar(winnr, '&scrollbind', 0)
1715 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1716 execute winbufnr(winnr).'bdelete'
1719 let bufnr = bufnr('')
1720 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1722 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1725 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1727 setlocal scrollbind nowrap nofoldenable
1728 let top = line('w0') + &scrolloff
1729 let current = line('.')
1730 let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1731 exe 'keepalt leftabove vsplit '.temp
1732 let b:fugitive_blamed_bufnr = bufnr
1733 let w:fugitive_leave = restore
1734 let b:fugitive_blame_arguments = join(a:args,' ')
1738 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1739 if exists('+concealcursor')
1740 setlocal concealcursor=nc conceallevel=2
1742 if exists('+relativenumber')
1743 setlocal norelativenumber
1745 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1746 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1747 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
1748 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1749 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>
1750 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1751 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
1752 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1753 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1754 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1755 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1756 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1757 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1758 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1759 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1770 return 'echoerr v:errmsg'
1774 function! s:BlameCommit(cmd) abort
1775 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1776 if cmd =~# '^echoerr'
1779 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1780 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1782 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1785 if search('^diff .* b/\M'.escape(path,'\').'$','W')
1787 let head = line('.')
1788 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1789 let top = +matchstr(getline('.'),' +\zs\d\+')
1790 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
1791 if lnum >= top && lnum <= top + len
1792 let offset = lnum - top
1800 while offset > 0 && line('.') < line('$')
1802 if getline('.') =~# '^[ +]'
1815 function! s:BlameJump(suffix) abort
1816 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1817 if commit =~# '^0\+$'
1820 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1821 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1823 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1825 let args = b:fugitive_blame_arguments
1826 let offset = line('.') - line('w0')
1827 let bufnr = bufnr('%')
1828 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1830 exe winnr.'wincmd w'
1832 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
1837 execute 'Gblame '.args
1839 let delta = line('.') - line('w0') - offset
1841 execute 'normal! '.delta."\<C-E>"
1843 execute 'normal! '.(-delta)."\<C-Y>"
1849 function! s:BlameSyntax() abort
1850 let b:current_syntax = 'fugitiveblame'
1851 let conceal = has('conceal') ? ' conceal' : ''
1852 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
1853 syn match FugitiveblameBoundary "^\^"
1854 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1855 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1856 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1857 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1858 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1859 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
1860 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
1861 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
1862 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
1863 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
1864 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1865 hi def link FugitiveblameBoundary Keyword
1866 hi def link FugitiveblameHash Identifier
1867 hi def link FugitiveblameUncommitted Function
1868 hi def link FugitiveblameTime PreProc
1869 hi def link FugitiveblameLineNumber Number
1870 hi def link FugitiveblameOriginalFile String
1871 hi def link FugitiveblameOriginalLineNumber Float
1872 hi def link FugitiveblameShort FugitiveblameDelimiter
1873 hi def link FugitiveblameDelimiter Delimiter
1874 hi def link FugitiveblameNotCommittedYet Comment
1879 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1881 function! s:Browse(bang,line1,count,...) abort
1883 let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1885 let expanded = s:buffer().rev()
1887 let expanded = s:buffer().path('/')
1889 let expanded = s:buffer().expand(rev)
1891 let full = s:repo().translate(expanded)
1893 if full =~# '^fugitive://'
1894 let commit = matchstr(full,'://.*//\zs\w\+')
1895 let path = matchstr(full,'://.*//\w\+\zs/.*')
1897 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1901 let path = path[1:-1]
1902 elseif s:repo().bare()
1903 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1906 let path = full[strlen(s:repo().tree())+1:-1]
1907 if path =~# '^\.git/'
1909 elseif isdirectory(full)
1915 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1916 let body = readfile(s:repo().dir(path[5:-1]))[0]
1917 if body =~# '^\x\{40\}$'
1921 elseif body =~# '^ref: refs/'
1922 let path = '.git/' . matchstr(body,'ref: \zs.*')
1926 if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1927 let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1928 elseif path =~# '^\.git/refs/remotes/.'
1929 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1931 let remote = 'origin'
1932 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1933 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1934 let branch = s:sub(path,'^\.git/refs/\w+/','')
1936 if filereadable(s:repo().dir('refs/remotes/'.branch))
1937 let remote = matchstr(branch,'[^/]\+')
1938 let rev = rev[strlen(remote)+1:-1]
1941 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1944 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1945 if remote =~# '^\.\=$'
1946 let remote = 'origin'
1947 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1948 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1954 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1959 let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1961 let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count > 0 ? a:line1 : 0)
1965 call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1970 return 'echomsg '.string(url)
1972 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
1975 return 'echoerr v:errmsg'
1979 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1981 let domain_pattern = 'github\.com'
1982 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
1983 for domain in domains
1984 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
1986 let repo = matchstr(a:url,'^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
1990 if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
1991 let root = 'http://' . s:sub(repo,':','/')
1993 let root = 'https://' . s:sub(repo,':','/')
1995 if path =~# '^\.git/refs/heads/'
1996 let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1998 return root . '/commits/' . path[16:-1]
2000 return root . '/commits/' . branch
2002 elseif path =~# '^\.git/refs/.'
2003 return root . '/commits/' . matchstr(path,'[^/]\+$')
2004 elseif path =~# '.git/\%(config$\|hooks\>\)'
2005 return root . '/admin'
2006 elseif path =~# '^\.git\>'
2009 if a:rev =~# '^[[:alnum:]._-]\+:'
2010 let commit = matchstr(a:rev,'^[^:]*')
2011 elseif a:commit =~# '^\d\=$'
2012 let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
2013 let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2018 let commit = a:commit
2021 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2022 elseif a:type == 'blob'
2023 let url = root . '/blob/' . commit . '/' . path
2024 if a:line2 > 0 && a:line1 == a:line2
2025 let url .= '#L' . a:line1
2027 let url .= '#L' . a:line1 . '-' . a:line2
2029 elseif a:type == 'tag'
2030 let commit = matchstr(getline(3),'^tag \zs.*')
2031 let url = root . '/tree/' . commit
2033 let url = root . '/commit/' . commit
2038 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
2039 let output = a:repo.git_chomp('instaweb','-b','unknown')
2040 if output =~# 'http://'
2041 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
2045 if a:path =~# '^\.git/refs/.'
2046 return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
2047 elseif a:path =~# '^\.git\>'
2051 if a:commit =~# '^\x\{40\}$'
2052 if a:type ==# 'commit'
2053 let url .= ';a=commit'
2055 let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
2057 if a:type ==# 'blob'
2058 let tmp = tempname()
2059 silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
2060 let url .= ';h=' . readfile(tmp)[0]
2063 let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
2065 call s:throw('fugitive: cannot browse uncommitted file')
2068 let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
2071 let url .= ';f=' . a:path
2074 let url .= '#l' . a:1
2079 " Section: File access
2081 function! s:ReplaceCmd(cmd,...) abort
2082 let fn = expand('%:p')
2083 let tmp = tempname()
2088 let old_index = $GIT_INDEX_FILE
2089 let $GIT_INDEX_FILE = a:1
2091 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2095 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2096 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2098 call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2101 if exists('old_index')
2102 let $GIT_INDEX_FILE = old_index
2105 silent exe 'keepalt file '.tmp
2110 silent exe 'keepalt file '.s:fnameescape(fn)
2111 catch /^Vim\%((\a\+)\)\=:E302/
2114 if fnamemodify(bufname('$'), ':p') ==# tmp
2115 silent execute 'bwipeout '.bufnr('$')
2117 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2121 function! s:BufReadIndex() abort
2122 if !exists('b:fugitive_display_format')
2123 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2125 let b:fugitive_display_format = b:fugitive_display_format % 2
2126 let b:fugitive_type = 'index'
2128 let b:git_dir = s:repo().dir()
2129 setlocal noro ma nomodeline
2130 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2133 let index = expand('%:p')
2135 if b:fugitive_display_format
2136 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2139 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2141 if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2142 let cmd = s:repo().git_command('status')
2144 let cmd = s:repo().git_command(
2145 \ '-c', 'status.displayCommentPrefix=true',
2146 \ '-c', 'color.status=false',
2147 \ '-c', 'status.short=false',
2151 execute cd.'`=s:repo().tree()`'
2152 call s:ReplaceCmd(cmd, index)
2157 set foldtext=fugitive#foldtext()
2159 setlocal ro noma nomod noswapfile
2160 if &bufhidden ==# ''
2161 setlocal bufhidden=delete
2166 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2167 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2168 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2169 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2170 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2171 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2172 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2173 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2174 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2175 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2176 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2177 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2178 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2179 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2180 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2181 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2182 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2183 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2184 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2185 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2186 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2187 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2188 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2189 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2190 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2191 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2193 return 'echoerr v:errmsg'
2197 function! s:FileRead() abort
2199 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2200 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2201 let hash = repo.rev_parse(path)
2205 let type = repo.git_chomp('cat-file','-t',hash)
2207 " TODO: use count, if possible
2208 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2210 return 'echoerr v:errmsg'
2214 function! s:BufReadIndexFile() abort
2216 let b:fugitive_type = 'blob'
2217 let b:git_dir = s:repo().dir()
2219 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2221 if &bufhidden ==# ''
2222 setlocal bufhidden=delete
2227 catch /^fugitive: rev-parse/
2228 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2231 return 'echoerr v:errmsg'
2235 function! s:BufWriteIndexFile() abort
2236 let tmp = tempname()
2238 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2239 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2240 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2241 let sha1 = readfile(tmp)[0]
2242 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2244 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2246 let info = old_mode.' '.sha1.' '.stage."\t".path
2247 call writefile([info],tmp)
2249 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2251 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2253 if v:shell_error == 0
2255 silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2256 call fugitive#reload_status()
2259 return 'echoerr '.string('fugitive: '.error)
2266 function! s:BufReadObject() abort
2269 let b:git_dir = s:repo().dir()
2270 let hash = s:buffer().sha1()
2271 if !exists("b:fugitive_type")
2272 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2274 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2275 return "echoerr 'fugitive: unrecognized git type'"
2277 let firstline = getline('.')
2278 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2279 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2282 if b:fugitive_type !=# 'blob'
2286 let pos = getpos('.')
2287 silent keepjumps %delete_
2291 if b:fugitive_type ==# 'tree'
2292 let b:fugitive_display_format = b:fugitive_display_format % 2
2293 if b:fugitive_display_format
2294 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2296 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2298 elseif b:fugitive_type ==# 'tag'
2299 let b:fugitive_display_format = b:fugitive_display_format % 2
2300 if b:fugitive_display_format
2301 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2303 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2305 elseif b:fugitive_type ==# 'commit'
2306 let b:fugitive_display_format = b:fugitive_display_format % 2
2307 if b:fugitive_display_format
2308 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2310 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))
2311 keepjumps call search('^parent ')
2312 if getline('.') ==# 'parent '
2313 silent keepjumps delete_
2315 silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2317 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2319 silent keepjumps delete_
2323 elseif b:fugitive_type ==# 'blob'
2324 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2328 keepjumps call setpos('.',pos)
2329 setlocal ro noma nomod noswapfile
2330 if &bufhidden ==# ''
2331 setlocal bufhidden=delete
2333 if b:fugitive_type !=# 'blob'
2334 setlocal filetype=git foldmethod=syntax
2335 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2336 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2344 return 'echoerr v:errmsg'
2348 augroup fugitive_files
2350 autocmd BufReadCmd index{,.lock}
2351 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2352 \ exe s:BufReadIndex() |
2353 \ elseif filereadable(expand('<amatch>')) |
2357 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2358 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2359 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2360 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2361 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2362 autocmd FileType git
2363 \ if exists('b:git_dir') |
2364 \ call s:JumpInit() |
2368 " Section: Temp files
2370 if !exists('s:temp_files')
2371 let s:temp_files = {}
2374 augroup fugitive_temp
2376 autocmd BufNewFile,BufReadPost *
2377 \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2378 \ let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2379 \ let b:git_type = 'temp' |
2380 \ let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2381 \ call fugitive#detect(expand('<afile>:p')) |
2382 \ setlocal bufhidden=delete |
2383 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2387 " Section: Go to file
2389 function! s:JumpInit() abort
2390 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2392 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2393 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2394 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2395 nnoremap <buffer> <silent> - :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<Bar> if fugitive#buffer().type('tree')<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>
2396 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2397 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2398 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2399 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2400 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2401 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2402 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2403 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2404 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2408 function! s:GF(mode) abort
2410 let buffer = s:buffer()
2411 let myhash = buffer.sha1()
2412 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2413 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2416 if buffer.type('tree')
2417 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2418 if showtree && line('.') == 1
2420 elseif showtree && line('.') > 2
2421 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2422 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2423 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2426 elseif buffer.type('blob')
2427 let ref = expand("<cfile>")
2429 let sha1 = buffer.repo().rev_parse(ref)
2433 return s:Edit(a:mode,0,ref)
2439 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2440 let ref = matchstr(getline('.'),'\x\{40\}')
2441 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2442 return s:Edit(a:mode,0,file)
2444 elseif getline('.') =~# '^#\trenamed:.* -> '
2445 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2446 return s:Edit(a:mode,0,file)
2447 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2448 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2449 return s:Edit(a:mode,0,file)
2450 elseif getline('.') =~# '^#\t.'
2451 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2452 return s:Edit(a:mode,0,file)
2453 elseif getline('.') =~# ': needs merge$'
2454 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2455 return s:Edit(a:mode,0,file).'|Gdiff'
2457 elseif getline('.') ==# '# Not currently on any branch.'
2458 return s:Edit(a:mode,0,'HEAD')
2459 elseif getline('.') =~# '^# On branch '
2460 let file = 'refs/heads/'.getline('.')[12:]
2461 return s:Edit(a:mode,0,file)
2462 elseif getline('.') =~# "^# Your branch .*'"
2463 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2464 return s:Edit(a:mode,0,file)
2467 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2469 if getline('.') =~# '^ref: '
2470 let ref = strpart(getline('.'),5)
2472 elseif getline('.') =~# '^commit \x\{40\}\>'
2473 let ref = matchstr(getline('.'),'\x\{40\}')
2474 return s:Edit(a:mode,0,ref)
2476 elseif getline('.') =~# '^parent \x\{40\}\>'
2477 let ref = matchstr(getline('.'),'\x\{40\}')
2478 let line = line('.')
2480 while getline(line) =~# '^parent '
2484 return s:Edit(a:mode,0,ref)
2486 elseif getline('.') =~ '^tree \x\{40\}$'
2487 let ref = matchstr(getline('.'),'\x\{40\}')
2488 if s:repo().rev_parse(myhash.':') == ref
2489 let ref = myhash.':'
2491 return s:Edit(a:mode,0,ref)
2493 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2494 let ref = matchstr(getline('.'),'\x\{40\}')
2495 let type = matchstr(getline(line('.')+1),'type \zs.*')
2497 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2500 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2501 let ref = matchstr(getline('.'),'\x\{40\}')
2502 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2504 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2505 let ref = getline('.')[4:]
2507 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2508 let type = getline('.')[0]
2509 let lnum = line('.') - 1
2511 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2512 if getline(lnum) =~# '^[ '.type.']'
2517 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2518 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2519 let dcmd = '+'.offset.'|normal! zv'
2522 elseif getline('.') =~# '^rename from '
2523 let ref = 'a/'.getline('.')[12:]
2524 elseif getline('.') =~# '^rename to '
2525 let ref = 'b/'.getline('.')[10:]
2527 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2528 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2529 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2532 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2533 let line = getline(line('.')-1)
2534 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2535 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2538 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2539 let ref = getline('.')
2545 let ref = s:sub(ref,'^a/','HEAD:')
2546 let ref = s:sub(ref,'^b/',':0:')
2548 let dref = s:sub(dref,'^a/','HEAD:')
2551 let ref = s:sub(ref,'^a/',myhash.'^:')
2552 let ref = s:sub(ref,'^b/',myhash.':')
2554 let dref = s:sub(dref,'^a/',myhash.'^:')
2558 if ref ==# '/dev/null'
2560 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2564 return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2566 return s:Edit(a:mode,0,ref)
2572 return 'echoerr v:errmsg'
2576 " Section: Statusline
2578 function! s:repo_head_ref() dict abort
2579 if !filereadable(self.dir('HEAD'))
2582 return readfile(self.dir('HEAD'))[0]
2585 call s:add_methods('repo',['head_ref'])
2587 function! fugitive#statusline(...) abort
2588 if !exists('b:git_dir')
2592 if s:buffer().commit() != ''
2593 let status .= ':' . s:buffer().commit()[0:7]
2595 let status .= '('.fugitive#head(7).')'
2596 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2597 return ',GIT'.status
2599 return '[Git'.status.']'
2603 function! fugitive#head(...) abort
2604 if !exists('b:git_dir')
2608 return s:repo().head(a:0 ? a:1 : 0)
2613 function! fugitive#foldtext() abort
2614 if &foldmethod !=# 'syntax'
2616 elseif getline(v:foldstart) =~# '^diff '
2617 let [add, remove] = [-1, -1]
2619 for lnum in range(v:foldstart, v:foldend)
2620 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2621 let filename = getline(lnum)[6:-1]
2623 if getline(lnum) =~# '^+'
2625 elseif getline(lnum) =~# '^-'
2627 elseif getline(lnum) =~# '^Binary '
2632 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2635 let filename = getline(v:foldstart)[5:-1]
2638 return 'Binary: '.filename
2640 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2642 elseif getline(v:foldstart) =~# '^# .*:$'
2643 let lines = getline(v:foldstart, v:foldend)
2644 call filter(lines, 'v:val =~# "^#\t"')
2645 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2646 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2647 return getline(v:foldstart).' '.join(lines, ', ')
2652 augroup fugitive_foldtext
2654 autocmd User Fugitive
2655 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2656 \ set foldtext=fugitive#foldtext() |