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 &shell =~? 'cmd' || 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.git_chomp('checkout', '--', filename)
797 call repo.git_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')
886 if filename =~ ' -> '
887 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
888 let filename = cmd[-1]
889 elseif section ==# 'staged'
890 let cmd = ['reset','-q','--',filename]
891 elseif getline(lnum) =~# '^#\tdeleted:'
892 let cmd = ['rm','--',filename]
893 elseif getline(lnum) =~# '^#\tmodified:'
894 let cmd = ['add','--',filename]
896 let cmd = ['add','-A','--',filename]
898 if !exists('first_filename')
899 let first_filename = 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 " Section: Ggrep, Glog
1062 if !exists('g:fugitive_summary_format')
1063 let g:fugitive_summary_format = '%s'
1066 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1067 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1068 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<count>,<f-args>)")
1069 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<count>,<f-args>)")
1071 function! s:Grep(cmd,bang,arg) abort
1072 let grepprg = &grepprg
1073 let grepformat = &grepformat
1074 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1077 execute cd.'`=s:repo().tree()`'
1078 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1079 let &grepformat = '%f:%l:%m'
1080 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1081 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1083 if bufname(entry.bufnr) =~ ':'
1084 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1087 elseif a:arg =~# '\%(^\| \)--cached\>'
1088 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1093 if a:cmd =~# '^l' && exists('changed')
1094 call setloclist(0, list, 'r')
1095 elseif exists('changed')
1096 call setqflist(list, 'r')
1098 if !a:bang && !empty(list)
1099 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1101 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1104 let &grepprg = grepprg
1105 let &grepformat = grepformat
1110 function! s:Log(cmd, count, ...) abort
1111 let path = s:buffer().path('/')
1112 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1115 let cmd = ['--no-pager', 'log', '--no-color']
1116 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.':'.(a:count ? a:count : '').'::'.g:fugitive_summary_format]
1117 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1118 if s:buffer().commit() =~# '\x\{40\}'
1119 let cmd += [s:buffer().commit()]
1120 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1121 let cmd += [s:buffer().path()[5:-1]]
1124 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1126 let cmd += ['--',path[1:-1]]
1128 let grepformat = &grepformat
1129 let grepprg = &grepprg
1130 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1133 execute cd.'`=s:repo().tree()`'
1134 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1135 let &grepformat = '%f:%l::%m,%f:::%m'
1138 let &grepformat = grepformat
1139 let &grepprg = grepprg
1144 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1146 function! s:Edit(cmd,bang,...) abort
1147 let buffer = s:buffer()
1149 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1152 let mywinnr = winnr()
1153 for winnr in range(winnr('$'),1,-1)
1154 if winnr != mywinnr && getwinvar(winnr,'&diff')
1155 execute winnr.'wincmd w'
1167 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1168 let args = join(arglist, ' ')
1170 let git = buffer.repo().git_command()
1171 let last = line('$')
1172 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1174 silent execute '1,'.last.'delete_'
1176 call fugitive#reload_status()
1178 return 'redraw|echo '.string(':!'.git.' '.args)
1180 let temp = resolve(tempname())
1181 let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1182 silent execute a:cmd.' '.temp
1183 if a:cmd =~# 'pedit'
1186 let echo = s:Edit('read',1,args)
1188 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1189 if getline(1) !~# '^diff '
1190 setlocal readonly nomodifiable
1192 if a:cmd =~# 'pedit'
1203 let file = buffer.expand(join(a:000, ' '))
1204 elseif expand('%') ==# ''
1206 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1207 let file = buffer.path(':')
1209 let file = buffer.path('/')
1212 let file = buffer.repo().translate(file)
1214 return 'echoerr v:errmsg'
1217 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1219 return a:cmd.' '.s:fnameescape(file)
1223 function! s:EditComplete(A,L,P) abort
1224 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1227 function! s:EditRunComplete(A,L,P) abort
1229 return s:GitComplete(a:A,a:L,a:P)
1231 return s:repo().superglob(a:A)
1235 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1236 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1237 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1238 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1239 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1240 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1241 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1243 " Section: Gwrite, Gwq
1245 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1246 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1247 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1249 function! s:Write(force,...) abort
1250 if exists('b:fugitive_commit_arguments')
1251 return 'write|bdelete'
1252 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1254 elseif s:buffer().type() == 'index'
1256 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1257 let filename = getline(4)[6:-1]
1260 setlocal buftype=nowrite
1261 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1262 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1264 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1267 let v:errmsg = split(err,"\n")[0]
1268 return 'echoerr v:errmsg'
1272 return 'Gedit '.fnameescape(filename)
1275 let mytab = tabpagenr()
1276 let mybufnr = bufnr('')
1277 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1278 if path =~# '^:\d\>'
1279 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1281 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1282 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) !=# ''
1283 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1284 return 'echoerr v:errmsg'
1286 let file = s:repo().translate(path)
1288 for nr in range(1,bufnr('$'))
1289 if fnamemodify(bufname(nr),':p') ==# file
1294 if treebufnr > 0 && treebufnr != bufnr('')
1295 let temp = tempname()
1296 silent execute '%write '.temp
1297 for tab in [mytab] + range(1,tabpagenr('$'))
1298 for winnr in range(1,tabpagewinnr(tab,'$'))
1299 if tabpagebuflist(tab)[winnr-1] == treebufnr
1300 execute 'tabnext '.tab
1302 execute winnr.'wincmd w'
1303 let restorewinnr = 1
1306 let lnum = line('.')
1307 let last = line('$')
1308 silent execute '$read '.temp
1309 silent execute '1,'.last.'delete_'
1314 if exists('restorewinnr')
1317 execute 'tabnext '.mytab
1323 call writefile(readfile(temp,'b'),file,'b')
1326 execute 'write! '.s:fnameescape(s:repo().translate(path))
1330 let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1332 let error = s:repo().git_chomp_in_tree('add', '--', path)
1335 let v:errmsg = 'fugitive: '.error
1336 return 'echoerr v:errmsg'
1338 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1342 let one = s:repo().translate(':1:'.path)
1343 let two = s:repo().translate(':2:'.path)
1344 let three = s:repo().translate(':3:'.path)
1345 for nr in range(1,bufnr('$'))
1346 let name = fnamemodify(bufname(nr), ':p')
1347 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1348 execute nr.'bdelete'
1353 let zero = s:repo().translate(':0:'.path)
1354 for tab in range(1,tabpagenr('$'))
1355 for winnr in range(1,tabpagewinnr(tab,'$'))
1356 let bufnr = tabpagebuflist(tab)[winnr-1]
1357 let bufname = fnamemodify(bufname(bufnr), ':p')
1358 if bufname ==# zero && bufnr != mybufnr
1359 execute 'tabnext '.tab
1361 execute winnr.'wincmd w'
1362 let restorewinnr = 1
1365 let lnum = line('.')
1366 let last = line('$')
1367 silent execute '$read '.s:fnameescape(file)
1368 silent execute '1,'.last.'delete_'
1373 if exists('restorewinnr')
1376 execute 'tabnext '.mytab
1382 call fugitive#reload_status()
1386 function! s:Wq(force,...) abort
1387 let bang = a:force ? '!' : ''
1388 if exists('b:fugitive_commit_arguments')
1391 let result = call(s:function('s:Write'),[a:force]+a:000)
1392 if result =~# '^\%(write\|wq\|echoerr\)'
1393 return s:sub(result,'^write','wq')
1395 return result.'|quit'.bang
1399 augroup fugitive_commit
1401 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1406 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('keepalt ',<f-args>)")
1407 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1408 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('',<f-args>)")
1410 augroup fugitive_diff
1412 autocmd BufWinLeave *
1413 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1414 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1416 autocmd BufWinEnter *
1417 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1418 \ call s:diffoff() |
1422 function! s:can_diffoff(buf) abort
1423 return getwinvar(bufwinnr(a:buf), '&diff') &&
1424 \ !empty(getbufvar(a:buf, 'git_dir')) &&
1425 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1428 function! fugitive#can_diffoff(buf) abort
1429 return s:can_diffoff(a:buf)
1432 function! s:diff_horizontal(count) abort
1433 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1434 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1435 return 'keepalt vert '
1436 elseif &diffopt =~# 'vertical'
1438 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1441 return 'keepalt vert '
1445 function! s:diff_window_count() abort
1447 for nr in range(1,winnr('$'))
1448 let c += getwinvar(nr,'&diff')
1453 function! s:diff_restore() abort
1454 let restore = 'setlocal nodiff noscrollbind'
1455 \ . ' scrollopt=' . &l:scrollopt
1456 \ . (&l:wrap ? ' wrap' : ' nowrap')
1457 \ . ' foldlevel=999'
1458 \ . ' foldmethod=' . &l:foldmethod
1459 \ . ' foldcolumn=' . &l:foldcolumn
1460 \ . ' foldlevel=' . &l:foldlevel
1461 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1462 if has('cursorbind')
1463 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1468 function! s:diffthis() abort
1470 let w:fugitive_diff_restore = s:diff_restore()
1475 function! s:diffoff() abort
1476 if exists('w:fugitive_diff_restore')
1477 execute w:fugitive_diff_restore
1478 unlet w:fugitive_diff_restore
1484 function! s:diffoff_all(dir) abort
1485 for nr in range(1,winnr('$'))
1486 if getwinvar(nr,'&diff')
1488 execute nr.'wincmd w'
1489 let restorewinnr = 1
1491 if exists('b:git_dir') && b:git_dir ==# a:dir
1498 function! s:buffer_compare_age(commit) dict abort
1499 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1500 let my_score = get(scores,':'.self.commit(),0)
1501 let their_score = get(scores,':'.a:commit,0)
1502 if my_score || their_score
1503 return my_score < their_score ? -1 : my_score != their_score
1504 elseif self.commit() ==# a:commit
1507 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1508 if base ==# self.commit()
1510 elseif base ==# a:commit
1513 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1514 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1515 return my_time < their_time ? -1 : my_time != their_time
1518 call s:add_methods('buffer',['compare_age'])
1520 function! s:Diff(vert,...) abort
1521 let vert = empty(a:vert) ? s:diff_horizontal(2) : a:vert
1522 if exists(':DiffGitCached')
1523 return 'DiffGitCached'
1524 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1525 let vert = empty(a:vert) ? s:diff_horizontal(3) : a:vert
1527 execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1528 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1531 execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1532 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1538 let arg = join(a:000, ' ')
1542 let file = s:buffer().path('/')
1544 let file = s:buffer().path(':0:')
1545 elseif arg =~# '^:/.'
1547 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1549 return 'echoerr v:errmsg'
1552 let file = s:buffer().expand(arg)
1554 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1555 let file = file.s:buffer().path(':')
1558 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1561 let spec = s:repo().translate(file)
1562 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1563 let restore = s:diff_restore()
1564 if exists('+cursorbind')
1567 let w:fugitive_diff_restore = restore
1568 if s:buffer().compare_age(commit) < 0
1569 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1571 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1573 let w:fugitive_diff_restore = restore
1575 if getwinvar('#', '&diff')
1577 call feedkeys("\<C-W>p", 'n')
1581 return 'echoerr v:errmsg'
1585 " Section: Gmove, Gremove
1587 function! s:Move(force,destination) abort
1588 if a:destination =~# '^/'
1589 let destination = a:destination[1:-1]
1591 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1592 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1593 let destination = destination[strlen(s:repo().tree('')):-1]
1596 if isdirectory(s:buffer().spec())
1597 " Work around Vim parser idiosyncrasy
1598 let discarded = s:buffer().setvar('&swapfile',0)
1600 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1602 let v:errmsg = 'fugitive: '.message
1603 return 'echoerr v:errmsg'
1605 let destination = s:repo().tree(destination)
1606 if isdirectory(destination)
1607 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1609 call fugitive#reload_status()
1610 if s:buffer().commit() == ''
1611 if isdirectory(destination)
1612 return 'keepalt edit '.s:fnameescape(destination)
1614 return 'keepalt saveas! '.s:fnameescape(destination)
1617 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1621 function! s:MoveComplete(A,L,P) abort
1623 return s:repo().superglob(a:A)
1625 let matches = split(glob(a:A.'*'),"\n")
1626 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1631 function! s:Remove(force) abort
1632 if s:buffer().commit() ==# ''
1634 elseif s:buffer().commit() ==# '0'
1635 let cmd = ['rm','--cached']
1637 let v:errmsg = 'fugitive: rm not supported here'
1638 return 'echoerr v:errmsg'
1641 let cmd += ['--force']
1643 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1645 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1646 return 'echoerr '.string(v:errmsg)
1648 call fugitive#reload_status()
1649 return 'bdelete'.(a:force ? '!' : '')
1653 augroup fugitive_remove
1655 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1656 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1657 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1663 augroup fugitive_blame
1665 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1666 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1667 autocmd Syntax fugitiveblame call s:BlameSyntax()
1668 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
1671 function! s:linechars(pattern) abort
1672 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1673 if exists('*synconcealed') && &conceallevel > 1
1674 for col in range(1, chars)
1675 let chars -= synconcealed(line('.'), col)[0]
1681 function! s:Blame(bang,line1,line2,count,args) abort
1683 if s:buffer().path() == ''
1684 call s:throw('file or blob required')
1686 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1687 call s:throw('unsupported option')
1689 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1690 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1691 if s:buffer().commit() =~# '\D\|..'
1692 let cmd += [s:buffer().commit()]
1694 let cmd += ['--contents', '-']
1696 let cmd += ['--', s:buffer().path()]
1697 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1699 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1702 execute cd.'`=s:repo().tree()`'
1705 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1707 let error = resolve(tempname())
1708 let temp = error.'.fugitiveblame'
1710 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1712 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1719 call s:throw(join(readfile(error),"\n"))
1721 for winnr in range(winnr('$'),1,-1)
1722 call setwinvar(winnr, '&scrollbind', 0)
1723 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1724 execute winbufnr(winnr).'bdelete'
1727 let bufnr = bufnr('')
1728 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1730 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1733 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1735 setlocal scrollbind nowrap nofoldenable
1736 let top = line('w0') + &scrolloff
1737 let current = line('.')
1738 let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1739 exe 'keepalt leftabove vsplit '.temp
1740 let b:fugitive_blamed_bufnr = bufnr
1741 let w:fugitive_leave = restore
1742 let b:fugitive_blame_arguments = join(a:args,' ')
1746 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1747 if exists('+concealcursor')
1748 setlocal concealcursor=nc conceallevel=2
1750 if exists('+relativenumber')
1751 setlocal norelativenumber
1753 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1754 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1755 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
1756 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1757 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>
1758 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1759 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
1760 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1761 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1762 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1763 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1764 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1765 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1766 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1767 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1778 return 'echoerr v:errmsg'
1782 function! s:BlameCommit(cmd) abort
1783 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1784 if cmd =~# '^echoerr'
1787 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1788 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1790 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1793 if search('^diff .* b/\M'.escape(path,'\').'$','W')
1795 let head = line('.')
1796 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1797 let top = +matchstr(getline('.'),' +\zs\d\+')
1798 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
1799 if lnum >= top && lnum <= top + len
1800 let offset = lnum - top
1808 while offset > 0 && line('.') < line('$')
1810 if getline('.') =~# '^[ +]'
1823 function! s:BlameJump(suffix) abort
1824 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1825 if commit =~# '^0\+$'
1828 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1829 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1831 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1833 let args = b:fugitive_blame_arguments
1834 let offset = line('.') - line('w0')
1835 let bufnr = bufnr('%')
1836 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1838 exe winnr.'wincmd w'
1840 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
1845 execute 'Gblame '.args
1847 let delta = line('.') - line('w0') - offset
1849 execute 'normal! '.delta."\<C-E>"
1851 execute 'normal! '.(-delta)."\<C-Y>"
1857 function! s:BlameSyntax() abort
1858 let b:current_syntax = 'fugitiveblame'
1859 let conceal = has('conceal') ? ' conceal' : ''
1860 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
1861 syn match FugitiveblameBoundary "^\^"
1862 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1863 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1864 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1865 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1866 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1867 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
1868 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
1869 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
1870 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
1871 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
1872 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1873 hi def link FugitiveblameBoundary Keyword
1874 hi def link FugitiveblameHash Identifier
1875 hi def link FugitiveblameUncommitted Function
1876 hi def link FugitiveblameTime PreProc
1877 hi def link FugitiveblameLineNumber Number
1878 hi def link FugitiveblameOriginalFile String
1879 hi def link FugitiveblameOriginalLineNumber Float
1880 hi def link FugitiveblameShort FugitiveblameDelimiter
1881 hi def link FugitiveblameDelimiter Delimiter
1882 hi def link FugitiveblameNotCommittedYet Comment
1887 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1889 function! s:Browse(bang,line1,count,...) abort
1891 let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1893 let expanded = s:buffer().rev()
1895 let expanded = s:buffer().path('/')
1897 let expanded = s:buffer().expand(rev)
1899 let full = s:repo().translate(expanded)
1901 if full =~# '^fugitive://'
1902 let commit = matchstr(full,'://.*//\zs\w\+')
1903 let path = matchstr(full,'://.*//\w\+\zs/.*')
1905 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1909 let path = path[1:-1]
1910 elseif s:repo().bare()
1911 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1914 let path = full[strlen(s:repo().tree())+1:-1]
1915 if path =~# '^\.git/'
1917 elseif isdirectory(full)
1923 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1924 let body = readfile(s:repo().dir(path[5:-1]))[0]
1925 if body =~# '^\x\{40\}$'
1929 elseif body =~# '^ref: refs/'
1930 let path = '.git/' . matchstr(body,'ref: \zs.*')
1934 if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1935 let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1936 elseif path =~# '^\.git/refs/remotes/.'
1937 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1939 let remote = 'origin'
1940 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1941 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1942 let branch = s:sub(path,'^\.git/refs/\w+/','')
1944 if filereadable(s:repo().dir('refs/remotes/'.branch))
1945 let remote = matchstr(branch,'[^/]\+')
1946 let rev = rev[strlen(remote)+1:-1]
1949 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1952 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1953 if remote =~# '^\.\=$'
1954 let remote = 'origin'
1955 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1956 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1962 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1967 let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1969 let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count > 0 ? a:line1 : 0)
1973 call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1978 return 'echomsg '.string(url)
1980 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
1983 return 'echoerr v:errmsg'
1987 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1989 let domain_pattern = 'github\.com'
1990 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
1991 for domain in domains
1992 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
1994 let repo = matchstr(a:url,'^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
1998 if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
1999 let root = 'http://' . s:sub(repo,':','/')
2001 let root = 'https://' . s:sub(repo,':','/')
2003 if path =~# '^\.git/refs/heads/'
2004 let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
2006 return root . '/commits/' . path[16:-1]
2008 return root . '/commits/' . branch
2010 elseif path =~# '^\.git/refs/.'
2011 return root . '/commits/' . matchstr(path,'[^/]\+$')
2012 elseif path =~# '.git/\%(config$\|hooks\>\)'
2013 return root . '/admin'
2014 elseif path =~# '^\.git\>'
2017 if a:rev =~# '^[[:alnum:]._-]\+:'
2018 let commit = matchstr(a:rev,'^[^:]*')
2019 elseif a:commit =~# '^\d\=$'
2020 let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
2021 let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2026 let commit = a:commit
2029 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2030 elseif a:type == 'blob'
2031 let url = root . '/blob/' . commit . '/' . path
2032 if a:line2 > 0 && a:line1 == a:line2
2033 let url .= '#L' . a:line1
2035 let url .= '#L' . a:line1 . '-' . a:line2
2037 elseif a:type == 'tag'
2038 let commit = matchstr(getline(3),'^tag \zs.*')
2039 let url = root . '/tree/' . commit
2041 let url = root . '/commit/' . commit
2046 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
2047 let output = a:repo.git_chomp('instaweb','-b','unknown')
2048 if output =~# 'http://'
2049 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
2053 if a:path =~# '^\.git/refs/.'
2054 return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
2055 elseif a:path =~# '^\.git\>'
2059 if a:commit =~# '^\x\{40\}$'
2060 if a:type ==# 'commit'
2061 let url .= ';a=commit'
2063 let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
2065 if a:type ==# 'blob'
2066 let tmp = tempname()
2067 silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
2068 let url .= ';h=' . readfile(tmp)[0]
2071 let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
2073 call s:throw('fugitive: cannot browse uncommitted file')
2076 let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
2079 let url .= ';f=' . a:path
2082 let url .= '#l' . a:1
2087 " Section: File access
2089 function! s:ReplaceCmd(cmd,...) abort
2090 let fn = expand('%:p')
2091 let tmp = tempname()
2096 let old_index = $GIT_INDEX_FILE
2097 let $GIT_INDEX_FILE = a:1
2099 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2103 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2104 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2106 call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2109 if exists('old_index')
2110 let $GIT_INDEX_FILE = old_index
2113 silent exe 'keepalt file '.tmp
2118 silent exe 'keepalt file '.s:fnameescape(fn)
2119 catch /^Vim\%((\a\+)\)\=:E302/
2122 if fnamemodify(bufname('$'), ':p') ==# tmp
2123 silent execute 'bwipeout '.bufnr('$')
2125 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2129 function! s:BufReadIndex() abort
2130 if !exists('b:fugitive_display_format')
2131 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2133 let b:fugitive_display_format = b:fugitive_display_format % 2
2134 let b:fugitive_type = 'index'
2136 let b:git_dir = s:repo().dir()
2137 setlocal noro ma nomodeline
2138 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2141 let index = expand('%:p')
2143 if b:fugitive_display_format
2144 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2147 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2149 if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2150 let cmd = s:repo().git_command('status')
2152 let cmd = s:repo().git_command(
2153 \ '-c', 'status.displayCommentPrefix=true',
2154 \ '-c', 'color.status=false',
2155 \ '-c', 'status.short=false',
2159 execute cd.'`=s:repo().tree()`'
2160 call s:ReplaceCmd(cmd, index)
2165 set foldtext=fugitive#foldtext()
2167 setlocal ro noma nomod noswapfile
2168 if &bufhidden ==# ''
2169 setlocal bufhidden=delete
2174 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2175 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2176 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2177 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2178 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2179 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2180 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2181 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2182 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2183 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2184 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2185 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2186 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2187 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2188 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2189 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2190 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2191 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2192 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2193 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2194 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2195 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2196 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2197 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2198 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2199 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2201 return 'echoerr v:errmsg'
2205 function! s:FileRead() abort
2207 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2208 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2209 let hash = repo.rev_parse(path)
2213 let type = repo.git_chomp('cat-file','-t',hash)
2215 " TODO: use count, if possible
2216 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2218 return 'echoerr v:errmsg'
2222 function! s:BufReadIndexFile() abort
2224 let b:fugitive_type = 'blob'
2225 let b:git_dir = s:repo().dir()
2227 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2229 if &bufhidden ==# ''
2230 setlocal bufhidden=delete
2235 catch /^fugitive: rev-parse/
2236 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2239 return 'echoerr v:errmsg'
2243 function! s:BufWriteIndexFile() abort
2244 let tmp = tempname()
2246 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2247 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2248 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2249 let sha1 = readfile(tmp)[0]
2250 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2252 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2254 let info = old_mode.' '.sha1.' '.stage."\t".path
2255 call writefile([info],tmp)
2257 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2259 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2261 if v:shell_error == 0
2263 silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2264 call fugitive#reload_status()
2267 return 'echoerr '.string('fugitive: '.error)
2274 function! s:BufReadObject() abort
2277 let b:git_dir = s:repo().dir()
2278 let hash = s:buffer().sha1()
2279 if !exists("b:fugitive_type")
2280 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2282 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2283 return "echoerr 'fugitive: unrecognized git type'"
2285 let firstline = getline('.')
2286 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2287 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2290 if b:fugitive_type !=# 'blob'
2294 let pos = getpos('.')
2295 silent keepjumps %delete_
2299 if b:fugitive_type ==# 'tree'
2300 let b:fugitive_display_format = b:fugitive_display_format % 2
2301 if b:fugitive_display_format
2302 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2304 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2306 elseif b:fugitive_type ==# 'tag'
2307 let b:fugitive_display_format = b:fugitive_display_format % 2
2308 if b:fugitive_display_format
2309 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2311 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2313 elseif b:fugitive_type ==# 'commit'
2314 let b:fugitive_display_format = b:fugitive_display_format % 2
2315 if b:fugitive_display_format
2316 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2318 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))
2319 keepjumps call search('^parent ')
2320 if getline('.') ==# 'parent '
2321 silent keepjumps delete_
2323 silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2325 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2327 silent keepjumps delete_
2331 elseif b:fugitive_type ==# 'blob'
2332 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2336 keepjumps call setpos('.',pos)
2337 setlocal ro noma nomod noswapfile
2338 if &bufhidden ==# ''
2339 setlocal bufhidden=delete
2341 if b:fugitive_type !=# 'blob'
2342 setlocal filetype=git foldmethod=syntax
2343 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2344 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2352 return 'echoerr v:errmsg'
2356 augroup fugitive_files
2358 autocmd BufReadCmd index{,.lock}
2359 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2360 \ exe s:BufReadIndex() |
2361 \ elseif filereadable(expand('<amatch>')) |
2365 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2366 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2367 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2368 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2369 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2370 autocmd FileType git
2371 \ if exists('b:git_dir') |
2372 \ call s:JumpInit() |
2376 " Section: Temp files
2378 if !exists('s:temp_files')
2379 let s:temp_files = {}
2382 augroup fugitive_temp
2384 autocmd BufNewFile,BufReadPost *
2385 \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2386 \ let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2387 \ let b:git_type = 'temp' |
2388 \ let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2389 \ call fugitive#detect(expand('<afile>:p')) |
2390 \ setlocal bufhidden=delete |
2391 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2395 " Section: Go to file
2397 function! s:JumpInit() abort
2398 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2400 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2401 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2402 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2403 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>
2404 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2405 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2406 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2407 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2408 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2409 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2410 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2411 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2412 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2416 function! s:GF(mode) abort
2418 let buffer = s:buffer()
2419 let myhash = buffer.sha1()
2420 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2421 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2424 if buffer.type('tree')
2425 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2426 if showtree && line('.') == 1
2428 elseif showtree && line('.') > 2
2429 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2430 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2431 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2434 elseif buffer.type('blob')
2435 let ref = expand("<cfile>")
2437 let sha1 = buffer.repo().rev_parse(ref)
2441 return s:Edit(a:mode,0,ref)
2447 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2448 let ref = matchstr(getline('.'),'\x\{40\}')
2449 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2450 return s:Edit(a:mode,0,file)
2452 elseif getline('.') =~# '^#\trenamed:.* -> '
2453 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2454 return s:Edit(a:mode,0,file)
2455 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2456 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2457 return s:Edit(a:mode,0,file)
2458 elseif getline('.') =~# '^#\t.'
2459 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2460 return s:Edit(a:mode,0,file)
2461 elseif getline('.') =~# ': needs merge$'
2462 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2463 return s:Edit(a:mode,0,file).'|Gdiff'
2465 elseif getline('.') ==# '# Not currently on any branch.'
2466 return s:Edit(a:mode,0,'HEAD')
2467 elseif getline('.') =~# '^# On branch '
2468 let file = 'refs/heads/'.getline('.')[12:]
2469 return s:Edit(a:mode,0,file)
2470 elseif getline('.') =~# "^# Your branch .*'"
2471 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2472 return s:Edit(a:mode,0,file)
2475 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2477 if getline('.') =~# '^ref: '
2478 let ref = strpart(getline('.'),5)
2480 elseif getline('.') =~# '^commit \x\{40\}\>'
2481 let ref = matchstr(getline('.'),'\x\{40\}')
2482 return s:Edit(a:mode,0,ref)
2484 elseif getline('.') =~# '^parent \x\{40\}\>'
2485 let ref = matchstr(getline('.'),'\x\{40\}')
2486 let line = line('.')
2488 while getline(line) =~# '^parent '
2492 return s:Edit(a:mode,0,ref)
2494 elseif getline('.') =~ '^tree \x\{40\}$'
2495 let ref = matchstr(getline('.'),'\x\{40\}')
2496 if s:repo().rev_parse(myhash.':') == ref
2497 let ref = myhash.':'
2499 return s:Edit(a:mode,0,ref)
2501 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2502 let ref = matchstr(getline('.'),'\x\{40\}')
2503 let type = matchstr(getline(line('.')+1),'type \zs.*')
2505 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2508 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2509 let ref = matchstr(getline('.'),'\x\{40\}')
2510 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2512 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2513 let ref = getline('.')[4:]
2515 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2516 let type = getline('.')[0]
2517 let lnum = line('.') - 1
2519 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2520 if getline(lnum) =~# '^[ '.type.']'
2525 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2526 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2527 let dcmd = '+'.offset.'|normal! zv'
2530 elseif getline('.') =~# '^rename from '
2531 let ref = 'a/'.getline('.')[12:]
2532 elseif getline('.') =~# '^rename to '
2533 let ref = 'b/'.getline('.')[10:]
2535 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2536 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2537 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2540 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2541 let line = getline(line('.')-1)
2542 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2543 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2546 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2547 let ref = getline('.')
2549 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
2550 return s:Edit(a:mode,0,expand('<cword>'))
2557 let ref = s:sub(ref,'^a/','HEAD:')
2558 let ref = s:sub(ref,'^b/',':0:')
2560 let dref = s:sub(dref,'^a/','HEAD:')
2563 let ref = s:sub(ref,'^a/',myhash.'^:')
2564 let ref = s:sub(ref,'^b/',myhash.':')
2566 let dref = s:sub(dref,'^a/',myhash.'^:')
2570 if ref ==# '/dev/null'
2572 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2576 return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2578 return s:Edit(a:mode,0,ref)
2584 return 'echoerr v:errmsg'
2588 " Section: Statusline
2590 function! s:repo_head_ref() dict abort
2591 if !filereadable(self.dir('HEAD'))
2594 return readfile(self.dir('HEAD'))[0]
2597 call s:add_methods('repo',['head_ref'])
2599 function! fugitive#statusline(...) abort
2600 if !exists('b:git_dir')
2604 if s:buffer().commit() != ''
2605 let status .= ':' . s:buffer().commit()[0:7]
2607 let status .= '('.fugitive#head(7).')'
2608 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2609 return ',GIT'.status
2611 return '[Git'.status.']'
2615 function! fugitive#head(...) abort
2616 if !exists('b:git_dir')
2620 return s:repo().head(a:0 ? a:1 : 0)
2625 function! fugitive#foldtext() abort
2626 if &foldmethod !=# 'syntax'
2628 elseif getline(v:foldstart) =~# '^diff '
2629 let [add, remove] = [-1, -1]
2631 for lnum in range(v:foldstart, v:foldend)
2632 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2633 let filename = getline(lnum)[6:-1]
2635 if getline(lnum) =~# '^+'
2637 elseif getline(lnum) =~# '^-'
2639 elseif getline(lnum) =~# '^Binary '
2644 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2647 let filename = getline(v:foldstart)[5:-1]
2650 return 'Binary: '.filename
2652 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2654 elseif getline(v:foldstart) =~# '^# .*:$'
2655 let lines = getline(v:foldstart, v:foldend)
2656 call filter(lines, 'v:val =~# "^#\t"')
2657 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2658 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2659 return getline(v:foldstart).' '.join(lines, ', ')
2664 augroup fugitive_foldtext
2666 autocmd User Fugitive
2667 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2668 \ set foldtext=fugitive#foldtext() |