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)
146 return simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??'))
148 if fugitive#is_git_dir($GIT_DIR)
149 " Ensure that we've cached the worktree
150 call s:configured_tree(simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??')))
151 if has_key(s:dir_for_worktree, root)
152 return s:dir_for_worktree[root]
155 let dir = s:sub(root, '[\/]$', '') . '/.git'
156 let type = getftype(dir)
157 if type ==# 'dir' && fugitive#is_git_dir(dir)
159 elseif type ==# 'link' && fugitive#is_git_dir(dir)
161 elseif type !=# '' && filereadable(dir)
162 let line = get(readfile(dir, '', 1), 0, '')
163 if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
164 return simplify(root.'/'.line[8:-1])
165 elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
168 elseif fugitive#is_git_dir(root)
172 let root = fnamemodify(root, ':h')
177 function! fugitive#detect(path) abort
178 if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
181 if !exists('b:git_dir')
182 let dir = fugitive#extract_git_dir(a:path)
187 if exists('b:git_dir')
188 if exists('#User#FugitiveBoot')
190 let [save_mls, &modelines] = [&mls, 0]
191 doautocmd User FugitiveBoot
196 if !exists('g:fugitive_no_maps')
197 cnoremap <buffer> <expr> <C-R><C-G> fnameescape(<SID>recall())
198 nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
200 let buffer = fugitive#buffer()
201 if expand('%:p') =~# '//'
202 call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
204 if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
205 if filereadable(b:git_dir.'/tags')
206 call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
208 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
209 call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
213 let [save_mls, &modelines] = [&mls, 0]
214 doautocmd User Fugitive
223 autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('%:p'))
224 autocmd FileType netrw call fugitive#detect(expand('%:p'))
225 autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTreeRoot.path.str())
226 autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
227 autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
228 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
231 " Section: Repository
233 let s:repo_prototype = {}
235 let s:worktree_for_dir = {}
236 let s:dir_for_worktree = {}
238 function! s:repo(...) abort
239 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
241 if has_key(s:repos, dir)
242 let repo = get(s:repos, dir)
244 let repo = {'git_dir': dir}
245 let s:repos[dir] = repo
247 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
249 call s:throw('not a git repository: '.expand('%:p'))
252 function! fugitive#repo(...) abort
253 return call('s:repo', a:000)
256 function! s:repo_dir(...) dict abort
257 return join([self.git_dir]+a:000,'/')
260 function! s:configured_tree(git_dir) abort
261 if !has_key(s:worktree_for_dir, a:git_dir)
262 let s:worktree_for_dir[a:git_dir] = ''
263 let config_file = a:git_dir . '/config'
264 if filereadable(config_file)
265 let config = readfile(config_file,'',10)
266 call filter(config,'v:val =~# "^\\s*worktree *="')
268 let s:worktree_for_dir[a:git_dir] = matchstr(config[0], '= *\zs.*')
269 let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
273 if s:worktree_for_dir[a:git_dir] =~# '^\.'
274 return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
276 return s:worktree_for_dir[a:git_dir]
280 function! s:repo_tree(...) dict abort
281 if self.dir() =~# '/\.git$'
282 let dir = self.dir()[0:-6]
284 let dir = s:configured_tree(self.git_dir)
287 call s:throw('no work tree')
289 return join([dir]+a:000,'/')
293 function! s:repo_bare() dict abort
294 if self.dir() =~# '/\.git$'
297 return s:configured_tree(self.git_dir) ==# ''
301 function! s:repo_translate(spec) dict abort
302 if a:spec ==# '.' || a:spec ==# '/.'
303 return self.bare() ? self.dir() : self.tree()
304 elseif a:spec =~# '^/\=\.git$' && self.bare()
306 elseif a:spec =~# '^/\=\.git/'
307 return self.dir(s:sub(a:spec, '^/=\.git/', ''))
308 elseif a:spec =~# '^/'
309 return self.tree().a:spec
310 elseif a:spec =~# '^:[0-3]:'
311 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
312 elseif a:spec ==# ':'
313 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
314 return fnamemodify($GIT_INDEX_FILE,':p')
316 return self.dir('index')
318 elseif a:spec =~# '^:/'
319 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
320 return 'fugitive://'.self.dir().'//'.ref
321 elseif a:spec =~# '^:'
322 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
323 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
324 return self.dir(a:spec)
325 elseif filereadable(self.dir('refs/'.a:spec))
326 return self.dir('refs/'.a:spec)
327 elseif filereadable(self.dir('refs/tags/'.a:spec))
328 return self.dir('refs/tags/'.a:spec)
329 elseif filereadable(self.dir('refs/heads/'.a:spec))
330 return self.dir('refs/heads/'.a:spec)
331 elseif filereadable(self.dir('refs/remotes/'.a:spec))
332 return self.dir('refs/remotes/'.a:spec)
333 elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
334 return self.dir('refs/remotes/'.a:spec,'/HEAD')
337 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
338 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
339 return 'fugitive://'.self.dir().'//'.ref.path
341 return self.tree(a:spec)
346 function! s:repo_head(...) dict abort
347 let head = s:repo().head_ref()
350 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
351 elseif head =~# '^\x\{40\}$'
352 " truncate hash to a:1 characters if we're in detached head mode
353 let len = a:0 ? a:1 : 0
354 let branch = len ? head[0:len-1] : ''
362 call s:add_methods('repo',['dir','tree','bare','translate','head'])
364 function! s:repo_git_command(...) dict abort
365 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
366 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
369 function! s:repo_git_chomp(...) dict abort
370 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
373 function! s:repo_git_chomp_in_tree(...) dict abort
374 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
377 execute cd.'`=s:repo().tree()`'
378 return call(s:repo().git_chomp, a:000, s:repo())
384 function! s:repo_rev_parse(rev) dict abort
385 let hash = self.git_chomp('rev-parse','--verify',a:rev)
386 if hash =~ '\<\x\{40\}$'
387 return matchstr(hash,'\<\x\{40\}$')
389 call s:throw('rev-parse '.a:rev.': '.hash)
392 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
394 function! s:repo_dirglob(base) dict abort
395 let base = s:sub(a:base,'^/','')
396 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
397 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
401 function! s:repo_superglob(base) dict abort
402 if a:base =~# '^/' || a:base !~# ':'
405 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
406 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
408 if filereadable(s:repo().dir('refs/stash'))
409 let heads += ["stash"]
410 let heads += sort(split(s:repo().git_chomp("stash","list","--pretty=format:%gd"),"\n"))
412 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
416 let base = s:sub(a:base,'^/','')
417 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
418 call map(matches,'s:shellslash(v:val)')
419 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
420 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
421 let results += matches
425 elseif a:base =~# '^:'
426 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
427 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
428 if a:base !~# '^:[0-3]\%(:\|$\)'
429 call filter(entries,'v:val[1] == "0"')
430 call map(entries,'v:val[2:-1]')
432 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
436 let tree = matchstr(a:base,'.*[:/]')
437 let entries = split(self.git_chomp('ls-tree',tree),"\n")
438 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
439 call map(entries,'tree.s:sub(v:val,".*\t","")')
440 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
444 call s:add_methods('repo',['dirglob','superglob'])
446 function! s:repo_config(conf) dict abort
447 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
450 function! s:repo_user() dict abort
451 let username = s:repo().config('user.name')
452 let useremail = s:repo().config('user.email')
453 return username.' <'.useremail.'>'
456 function! s:repo_aliases() dict abort
457 if !has_key(self,'_aliases')
458 let self._aliases = {}
459 for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
460 let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
466 call s:add_methods('repo',['config', 'user', 'aliases'])
468 function! s:repo_keywordprg() dict abort
469 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
470 if has('gui_running') && !has('win32')
471 return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
473 return g:fugitive_git_executable . args . ' show'
477 call s:add_methods('repo',['keywordprg'])
481 let s:buffer_prototype = {}
483 function! s:buffer(...) abort
484 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
485 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
486 if buffer.getvar('git_dir') !=# ''
489 call s:throw('not a git repository: '.expand('%:p'))
492 function! fugitive#buffer(...) abort
493 return s:buffer(a:0 ? a:1 : '%')
496 function! s:buffer_getvar(var) dict abort
497 return getbufvar(self['#'],a:var)
500 function! s:buffer_setvar(var,value) dict abort
501 return setbufvar(self['#'],a:var,a:value)
504 function! s:buffer_getline(lnum) dict abort
505 return get(getbufline(self['#'], a:lnum), 0, '')
508 function! s:buffer_repo() dict abort
509 return s:repo(self.getvar('git_dir'))
512 function! s:buffer_type(...) dict abort
513 if self.getvar('fugitive_type') != ''
514 let type = self.getvar('fugitive_type')
515 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
517 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
519 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
521 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
523 elseif isdirectory(self.spec())
524 let type = 'directory'
525 elseif self.spec() == ''
531 return !empty(filter(copy(a:000),'v:val ==# type'))
539 function! s:buffer_spec() dict abort
540 let bufname = bufname(self['#'])
542 for i in split(bufname,'[^:]\zs\\')
543 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
545 return s:shellslash(fnamemodify(retval,':p'))
550 function! s:buffer_spec() dict abort
551 let bufname = bufname(self['#'])
552 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
557 function! s:buffer_name() dict abort
561 function! s:buffer_commit() dict abort
562 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
565 function! s:cpath(path) abort
566 if exists('+fileignorecase') && &fileignorecase
567 return tolower(a:path)
573 function! s:buffer_path(...) dict abort
574 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
576 let rev = s:sub(rev,'\w*','')
577 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
578 \ s:cpath(self.repo().dir() . '/')
579 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
580 elseif !self.repo().bare() &&
581 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
582 \ s:cpath(self.repo().tree() . '/')
583 let rev = self.spec()[strlen(self.repo().tree()) : -1]
585 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
588 function! s:buffer_rev() dict abort
589 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
591 return ':'.rev[0].':'.rev[2:-1]
593 return s:sub(rev,'/',':')
594 elseif self.spec() =~ '\.git/index$'
596 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
597 return self.spec()[strlen(self.repo().dir())+1 : -1]
599 return self.path('/')
603 function! s:buffer_sha1() dict abort
604 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
605 return self.repo().rev_parse(self.rev())
611 function! s:buffer_expand(rev) dict abort
612 if a:rev =~# '^:[0-3]$'
613 let file = a:rev.self.path(':')
614 elseif a:rev =~# '^[-:]/$'
615 let file = '/'.self.path()
616 elseif a:rev =~# '^-'
617 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
618 elseif a:rev =~# '^@{'
619 let file = 'HEAD'.a:rev.self.path(':')
620 elseif a:rev =~# '^[~^]'
621 let commit = s:sub(self.commit(),'^\d=$','HEAD')
622 let file = commit.a:rev.self.path(':')
626 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
629 function! s:buffer_containing_commit() dict abort
630 if self.commit() =~# '^\d$'
632 elseif self.commit() =~# '.'
639 function! s:buffer_up(...) dict abort
641 let c = a:0 ? a:1 : 1
647 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
649 elseif rev =~# '^/\|:.*/'
650 let rev = s:sub(rev, '.*\zs/.*', '')
652 let rev = matchstr(rev, '^[^:]*:')
663 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
667 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
669 function! s:ExecuteInTree(cmd) abort
670 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
673 execute cd.'`=s:repo().tree()`'
680 function! s:Git(bang, args) abort
682 return s:Edit('edit', 1, a:args)
684 let git = g:fugitive_git_executable
685 if has('gui_running') && !has('win32')
686 let git .= ' --no-pager'
688 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
689 if exists(':terminal')
690 let dir = s:repo().tree()
692 execute 'lcd' fnameescape(dir)
693 execute 'terminal' git args
695 call s:ExecuteInTree('!'.git.' '.args)
697 call fugitive#reload_status()
700 return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
703 function! fugitive#git_commands() abort
704 if !exists('s:exec_path')
705 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
707 return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
710 function! s:GitComplete(A, L, P) abort
711 if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
712 let cmds = fugitive#git_commands()
713 return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
715 return s:repo().superglob(a:A)
721 function! s:DirComplete(A,L,P) abort
722 let matches = s:repo().dirglob(a:A)
726 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>)`")
727 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>)`")
731 call s:command("-bar Gstatus :execute s:Status()")
732 augroup fugitive_status
735 autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
736 autocmd BufDelete term://* call fugitive#reload_status()
740 function! s:Status() abort
744 setlocal foldmethod=syntax foldlevel=1
745 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
747 return 'echoerr v:errmsg'
752 function! fugitive#reload_status() abort
753 if exists('s:reloading_status')
757 let s:reloading_status = 1
758 let mytab = tabpagenr()
759 for tab in [mytab] + range(1,tabpagenr('$'))
760 for winnr in range(1,tabpagewinnr(tab,'$'))
761 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
762 execute 'tabnext '.tab
764 execute winnr.'wincmd w'
769 call s:BufReadIndex()
772 if exists('restorewinnr')
775 execute 'tabnext '.mytab
781 unlet! s:reloading_status
785 function! s:stage_info(lnum) abort
786 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
788 if has('multi_byte_encoding')
789 let colon = '\%(:\|\%uff1a\)'
793 while lnum && getline(lnum) !~# colon.'$'
798 elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
799 return [matchstr(filename, colon.' *\zs.*'), 'staged']
800 elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
801 return [filename, 'untracked']
802 elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
803 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
804 elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
805 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
807 return ['', 'unknown']
811 function! s:StageNext(count) abort
812 for i in range(a:count)
813 call search('^#\t.*','W')
818 function! s:StagePrevious(count) abort
819 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
820 return 'CtrlP '.fnameescape(s:repo().tree())
822 for i in range(a:count)
823 call search('^#\t.*','Wbe')
829 function! s:StageReloadSeek(target,lnum1,lnum2) abort
831 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
832 if f !=# '' | let jump = f | endif
833 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
834 if f !=# '' | let jump = f | endif
838 call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
841 function! s:StageUndo() abort
842 let [filename, section] = s:stage_info(line('.'))
847 let hash = repo.git_chomp('hash-object', '-w', filename)
849 if section ==# 'untracked'
850 call delete(s:repo().tree(filename))
851 elseif section ==# 'unstaged'
852 call repo.git_chomp_in_tree('checkout', '--', filename)
854 call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
856 call s:StageReloadSeek(filename, line('.'), line('.'))
858 return 'checktime|redraw|echomsg ' .
859 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
863 function! s:StageDiff(diff) abort
864 let [filename, section] = s:stage_info(line('.'))
865 if filename ==# '' && section ==# 'staged'
866 return 'Git! diff --no-ext-diff --cached'
867 elseif filename ==# ''
868 return 'Git! diff --no-ext-diff'
869 elseif filename =~# ' -> '
870 let [old, new] = split(filename,' -> ')
871 execute 'Gedit '.s:fnameescape(':0:'.new)
872 return a:diff.' HEAD:'.s:fnameescape(old)
873 elseif section ==# 'staged'
874 execute 'Gedit '.s:fnameescape(':0:'.filename)
877 execute 'Gedit '.s:fnameescape('/'.filename)
882 function! s:StageDiffEdit() abort
883 let [filename, section] = s:stage_info(line('.'))
884 let arg = (filename ==# '' ? '.' : filename)
885 if section ==# 'staged'
886 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
887 elseif section ==# 'untracked'
889 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
893 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
894 call search('^# .*:$','W')
897 call s:StageReloadSeek(arg,line('.'),line('.'))
901 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
905 function! s:StageToggle(lnum1,lnum2) abort
906 if a:lnum1 == 1 && a:lnum2 == 1
907 return 'Gedit /.git|call search("^index$", "wc")'
911 for lnum in range(a:lnum1,a:lnum2)
912 let [filename, section] = s:stage_info(lnum)
914 if getline('.') =~# '^# .*:$'
915 if section ==# 'staged'
916 call repo.git_chomp_in_tree('reset','-q')
919 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
920 call search('^# .*:$','W')
923 elseif section ==# 'unstaged'
924 call repo.git_chomp_in_tree('add','-u')
927 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
928 call search('^# .*:$','W')
932 call repo.git_chomp_in_tree('add','.')
935 call search('^# .*:$','W')
943 if filename =~ ' -> '
944 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
945 let filename = cmd[-1]
946 elseif section ==# 'staged'
947 let cmd = ['reset','-q','--',filename]
948 elseif getline(lnum) =~# '^#\tdeleted:'
949 let cmd = ['rm','--',filename]
950 elseif getline(lnum) =~# '^#\tmodified:'
951 let cmd = ['add','--',filename]
953 let cmd = ['add','-A','--',filename]
955 if !exists('first_filename')
956 let first_filename = filename
958 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
960 if exists('first_filename')
961 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
963 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
965 return 'echoerr v:errmsg'
970 function! s:StagePatch(lnum1,lnum2) abort
974 for lnum in range(a:lnum1,a:lnum2)
975 let [filename, section] = s:stage_info(lnum)
976 if getline('.') =~# '^# .*:$' && section ==# 'staged'
977 return 'Git reset --patch'
978 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
979 return 'Git add --patch'
980 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
981 return 'Git add -N .'
982 elseif filename ==# ''
985 if !exists('first_filename')
986 let first_filename = filename
989 if filename =~ ' -> '
990 let reset += [split(filename,' -> ')[1]]
991 elseif section ==# 'staged'
992 let reset += [filename]
993 elseif getline(lnum) !~# '^#\tdeleted:'
994 let add += [filename]
999 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1002 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1004 if exists('first_filename')
1008 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1011 return 'echoerr v:errmsg'
1018 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
1020 function! s:Commit(args, ...) abort
1021 let repo = a:0 ? a:1 : s:repo()
1022 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1024 let msgfile = repo.dir('COMMIT_EDITMSG')
1025 let outfile = tempname()
1026 let errorfile = tempname()
1029 execute cd.s:fnameescape(repo.tree())
1032 let old_editor = $GIT_EDITOR
1033 let $GIT_EDITOR = 'false'
1035 let command = 'env GIT_EDITOR=false '
1037 let command .= repo.git_command('commit').' '.a:args
1039 noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1040 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1041 noautocmd execute '!'.command.' 2> '.errorfile
1043 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1048 if !has('gui_running')
1052 if filereadable(outfile)
1053 for line in readfile(outfile)
1059 let errors = readfile(errorfile)
1060 let error = get(errors,-2,get(errors,-1,'!'))
1061 if error =~# 'false''\=\.$'
1063 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1064 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1065 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1066 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1067 let args = '-F '.s:shellesc(msgfile).' '.args
1068 if args !~# '\%(^\| \)--cleanup\>'
1069 let args = '--cleanup=strip '.args
1071 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1072 execute 'keepalt edit '.s:fnameescape(msgfile)
1073 elseif a:args =~# '\%(^\| \)-\%(-verbose\|\w*v\)\>'
1074 execute 'keepalt '.(tabpagenr()-1).'tabedit '.s:fnameescape(msgfile)
1075 elseif s:buffer().type() ==# 'index'
1076 execute 'keepalt edit '.s:fnameescape(msgfile)
1077 execute (search('^#','n')+1).'wincmd+'
1078 setlocal nopreviewwindow
1080 execute 'keepalt split '.s:fnameescape(msgfile)
1082 let b:fugitive_commit_arguments = args
1083 setlocal bufhidden=wipe filetype=gitcommit
1085 elseif error ==# '!'
1092 return 'echoerr v:errmsg'
1094 if exists('old_editor')
1095 let $GIT_EDITOR = old_editor
1097 call delete(outfile)
1098 call delete(errorfile)
1099 call fugitive#reload_status()
1103 function! s:CommitComplete(A,L,P) abort
1104 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1105 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']
1106 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1108 return s:repo().superglob(a:A)
1112 function! s:FinishCommit() abort
1113 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1115 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1116 return s:Commit(args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1121 " Section: Gmerge, Gpull
1123 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1124 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1125 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1126 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1128 function! s:RevisionComplete(A, L, P) abort
1129 return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1130 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1133 function! s:RemoteComplete(A, L, P) abort
1134 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1136 let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1137 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1138 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1140 let matches = split(s:repo().git_chomp('remote'), "\n")
1142 return join(matches, "\n")
1145 function! fugitive#cwindow() abort
1146 if &buftype == 'quickfix'
1150 if &buftype == 'quickfix'
1156 let s:common_efm = ''
1158 \ . '%+Eusage:%.%#,'
1159 \ . '%+Eerror:%.%#,'
1160 \ . '%+Efatal:%.%#,'
1161 \ . '%-G%.%#%\e[K%.%#,'
1162 \ . '%-G%.%#%\r%.%\+'
1164 function! s:Merge(cmd, bang, args) abort
1165 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1167 let [mp, efm] = [&l:mp, &l:efm]
1168 let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1170 let &l:errorformat = ''
1171 \ . '%-Gerror:%.%#false''.,'
1172 \ . '%-G%.%# ''git commit'' %.%#,'
1173 \ . '%+Emerge:%.%#,'
1174 \ . s:common_efm . ','
1175 \ . '%+ECannot %.%#: You have unstaged changes.,'
1176 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1177 \ . '%+EThere is no tracking information for the current branch.,'
1178 \ . '%+EYou are not currently on a branch. Please specify which,'
1179 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1180 \ . 'CONFLICT (%m): Merge conflict in %f,'
1181 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1182 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1183 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1184 \ . '%+ECONFLICT %.%#,'
1185 \ . '%+EKONFLIKT %.%#,'
1186 \ . '%+ECONFLIT %.%#,'
1187 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1188 \ . "%+E\u51b2\u7a81 %.%#,"
1190 if a:cmd =~# '^merge' && empty(a:args) &&
1191 \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1192 \ !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1193 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1195 let &l:makeprg = s:sub(g:fugitive_git_executable . ' ' . a:cmd .
1196 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
1197 \ ' ' . a:args, ' *$', '')
1199 if !empty($GIT_EDITOR) || has('win32')
1200 let old_editor = $GIT_EDITOR
1201 let $GIT_EDITOR = 'false'
1203 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1205 execute cd fnameescape(s:repo().tree())
1206 silent noautocmd make!
1207 catch /^Vim\%((\a\+)\)\=:E211/
1208 let err = v:exception
1211 let [&l:mp, &l:efm] = [mp, efm]
1212 if exists('old_editor')
1213 let $GIT_EDITOR = old_editor
1215 execute cd fnameescape(cwd)
1217 call fugitive#reload_status()
1218 if empty(filter(getqflist(),'v:val.valid'))
1219 if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1221 return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1224 let qflist = getqflist()
1229 let e.pattern = '^<<<<<<<'
1232 call fugitive#cwindow()
1234 call setqflist(qflist, 'r')
1239 return exists('err') ? 'echoerr '.string(err) : ''
1242 " Section: Ggrep, Glog
1244 if !exists('g:fugitive_summary_format')
1245 let g:fugitive_summary_format = '%s'
1248 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1249 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1250 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1251 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1253 function! s:Grep(cmd,bang,arg) abort
1254 let grepprg = &grepprg
1255 let grepformat = &grepformat
1256 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1259 execute cd.'`=s:repo().tree()`'
1260 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
1261 let &grepformat = '%f:%l:%m,%f'
1262 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1263 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1265 if bufname(entry.bufnr) =~ ':'
1266 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1269 elseif a:arg =~# '\%(^\| \)--cached\>'
1270 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1275 if a:cmd =~# '^l' && exists('changed')
1276 call setloclist(0, list, 'r')
1277 elseif exists('changed')
1278 call setqflist(list, 'r')
1280 if !a:bang && !empty(list)
1281 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1283 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1286 let &grepprg = grepprg
1287 let &grepformat = grepformat
1292 function! s:Log(cmd, line1, line2, ...) abort
1293 let path = s:buffer().path('/')
1294 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1297 let cmd = ['--no-pager', 'log', '--no-color']
1298 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1299 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1300 if s:buffer().commit() =~# '\x\{40\}'
1301 let cmd += [s:buffer().commit()]
1302 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1303 let cmd += [s:buffer().path()[5:-1]]
1306 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1309 let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1311 let cmd += ['--', path[1:-1]]
1314 let grepformat = &grepformat
1315 let grepprg = &grepprg
1316 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1319 execute cd.'`=s:repo().tree()`'
1320 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1321 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1324 let &grepformat = grepformat
1325 let &grepprg = grepprg
1330 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1332 function! s:Edit(cmd,bang,...) abort
1333 let buffer = s:buffer()
1335 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1337 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
1338 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
1345 let mywinnr = winnr()
1346 for winnr in range(winnr('$'),1,-1)
1347 if winnr != mywinnr && getwinvar(winnr,'&diff')
1348 execute winnr.'wincmd w'
1360 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1361 let args = join(arglist, ' ')
1363 let git = buffer.repo().git_command()
1364 let last = line('$')
1365 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1367 silent execute '1,'.last.'delete_'
1369 call fugitive#reload_status()
1371 return 'redraw|echo '.string(':!'.git.' '.args)
1373 let temp = resolve(tempname())
1374 let s:temp_files[s:cpath(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1375 silent execute a:cmd.' '.temp
1376 if a:cmd =~# 'pedit'
1379 let echo = s:Edit('read',1,args)
1381 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1382 if getline(1) !~# '^diff '
1383 setlocal readonly nomodifiable
1385 if a:cmd =~# 'pedit'
1396 let file = buffer.expand(join(a:000, ' '))
1397 elseif expand('%') ==# ''
1399 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1400 let file = buffer.path(':')
1402 let file = buffer.path('/')
1405 let file = buffer.repo().translate(file)
1407 return 'echoerr v:errmsg'
1410 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1412 return a:cmd.' '.s:fnameescape(file)
1416 function! s:EditComplete(A,L,P) abort
1417 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1420 function! s:EditRunComplete(A,L,P) abort
1422 return s:GitComplete(a:A,a:L,a:P)
1424 return s:repo().superglob(a:A)
1428 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1429 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1430 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1431 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1432 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1433 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1434 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1436 " Section: Gwrite, Gwq
1438 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1439 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1440 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1442 function! s:Write(force,...) abort
1443 if exists('b:fugitive_commit_arguments')
1444 return 'write|bdelete'
1445 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1447 elseif s:buffer().type() == 'index'
1449 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1450 let filename = getline(4)[6:-1]
1453 setlocal buftype=nowrite
1454 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1455 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1457 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1460 let v:errmsg = split(err,"\n")[0]
1461 return 'echoerr v:errmsg'
1465 return 'Gedit '.fnameescape(filename)
1468 let mytab = tabpagenr()
1469 let mybufnr = bufnr('')
1470 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1472 return 'echoerr '.string('fugitive: cannot determine file path')
1474 if path =~# '^:\d\>'
1475 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1477 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1478 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) !=# ''
1479 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1480 return 'echoerr v:errmsg'
1482 let file = s:repo().translate(path)
1484 for nr in range(1,bufnr('$'))
1485 if fnamemodify(bufname(nr),':p') ==# file
1490 if treebufnr > 0 && treebufnr != bufnr('')
1491 let temp = tempname()
1492 silent execute '%write '.temp
1493 for tab in [mytab] + range(1,tabpagenr('$'))
1494 for winnr in range(1,tabpagewinnr(tab,'$'))
1495 if tabpagebuflist(tab)[winnr-1] == treebufnr
1496 execute 'tabnext '.tab
1498 execute winnr.'wincmd w'
1499 let restorewinnr = 1
1502 let lnum = line('.')
1503 let last = line('$')
1504 silent execute '$read '.temp
1505 silent execute '1,'.last.'delete_'
1510 if exists('restorewinnr')
1513 execute 'tabnext '.mytab
1519 call writefile(readfile(temp,'b'),file,'b')
1522 execute 'write! '.s:fnameescape(s:repo().translate(path))
1526 let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1528 let error = s:repo().git_chomp_in_tree('add', '--', path)
1531 let v:errmsg = 'fugitive: '.error
1532 return 'echoerr v:errmsg'
1534 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1538 let one = s:repo().translate(':1:'.path)
1539 let two = s:repo().translate(':2:'.path)
1540 let three = s:repo().translate(':3:'.path)
1541 for nr in range(1,bufnr('$'))
1542 let name = fnamemodify(bufname(nr), ':p')
1543 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1544 execute nr.'bdelete'
1549 let zero = s:repo().translate(':0:'.path)
1550 for tab in range(1,tabpagenr('$'))
1551 for winnr in range(1,tabpagewinnr(tab,'$'))
1552 let bufnr = tabpagebuflist(tab)[winnr-1]
1553 let bufname = fnamemodify(bufname(bufnr), ':p')
1554 if bufname ==# zero && bufnr != mybufnr
1555 execute 'tabnext '.tab
1557 execute winnr.'wincmd w'
1558 let restorewinnr = 1
1561 let lnum = line('.')
1562 let last = line('$')
1563 silent execute '$read '.s:fnameescape(file)
1564 silent execute '1,'.last.'delete_'
1569 if exists('restorewinnr')
1572 execute 'tabnext '.mytab
1578 call fugitive#reload_status()
1582 function! s:Wq(force,...) abort
1583 let bang = a:force ? '!' : ''
1584 if exists('b:fugitive_commit_arguments')
1587 let result = call(s:function('s:Write'),[a:force]+a:000)
1588 if result =~# '^\%(write\|wq\|echoerr\)'
1589 return s:sub(result,'^write','wq')
1591 return result.'|quit'.bang
1595 augroup fugitive_commit
1597 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1600 " Section: Gpush, Gfetch
1602 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
1603 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1605 function! s:Dispatch(bang, args)
1606 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1608 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1610 let b:current_compiler = 'git'
1611 let &l:errorformat = s:common_efm
1612 let &l:makeprg = g:fugitive_git_executable . ' ' . a:args
1613 execute cd fnameescape(s:repo().tree())
1614 if exists(':Make') == 2
1617 silent noautocmd make!
1619 return 'call fugitive#cwindow()'
1623 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1624 if empty(cc) | unlet! b:current_compiler | endif
1625 execute cd fnameescape(cwd)
1631 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
1632 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
1633 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
1635 augroup fugitive_diff
1637 autocmd BufWinLeave *
1638 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1639 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1641 autocmd BufWinEnter *
1642 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1643 \ call s:diffoff() |
1647 function! s:can_diffoff(buf) abort
1648 return getwinvar(bufwinnr(a:buf), '&diff') &&
1649 \ !empty(getbufvar(a:buf, 'git_dir')) &&
1650 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1653 function! fugitive#can_diffoff(buf) abort
1654 return s:can_diffoff(a:buf)
1657 function! s:diff_modifier(count) abort
1658 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1659 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1661 elseif &diffopt =~# 'vertical'
1662 return 'keepalt vert '
1663 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1666 return 'keepalt vert '
1670 function! s:diff_window_count() abort
1672 for nr in range(1,winnr('$'))
1673 let c += getwinvar(nr,'&diff')
1678 function! s:diff_restore() abort
1679 let restore = 'setlocal nodiff noscrollbind'
1680 \ . ' scrollopt=' . &l:scrollopt
1681 \ . (&l:wrap ? ' wrap' : ' nowrap')
1682 \ . ' foldlevel=999'
1683 \ . ' foldmethod=' . &l:foldmethod
1684 \ . ' foldcolumn=' . &l:foldcolumn
1685 \ . ' foldlevel=' . &l:foldlevel
1686 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1687 if has('cursorbind')
1688 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1693 function! s:diffthis() abort
1695 let w:fugitive_diff_restore = s:diff_restore()
1700 function! s:diffoff() abort
1701 if exists('w:fugitive_diff_restore')
1702 execute w:fugitive_diff_restore
1703 unlet w:fugitive_diff_restore
1709 function! s:diffoff_all(dir) abort
1710 let curwin = winnr()
1711 for nr in range(1,winnr('$'))
1712 if getwinvar(nr,'&diff')
1714 execute nr.'wincmd w'
1715 let restorewinnr = 1
1717 if exists('b:git_dir') && b:git_dir ==# a:dir
1722 execute curwin.'wincmd w'
1725 function! s:buffer_compare_age(commit) dict abort
1726 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1727 let my_score = get(scores,':'.self.commit(),0)
1728 let their_score = get(scores,':'.a:commit,0)
1729 if my_score || their_score
1730 return my_score < their_score ? -1 : my_score != their_score
1731 elseif self.commit() ==# a:commit
1734 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1735 if base ==# self.commit()
1737 elseif base ==# a:commit
1740 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1741 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1742 return my_time < their_time ? -1 : my_time != their_time
1745 call s:add_methods('buffer',['compare_age'])
1747 function! s:Diff(vert,keepfocus,...) abort
1748 let args = copy(a:000)
1750 if get(args, 0) =~# '^+'
1751 let post = remove(args, 0)[1:-1]
1753 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1754 if exists(':DiffGitCached')
1755 return 'DiffGitCached'
1756 elseif (empty(args) || args[0] == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1757 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1759 execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1760 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1763 execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1764 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1770 let arg = join(args, ' ')
1774 let file = s:buffer().path('/')
1776 let file = s:buffer().path(':0:')
1777 elseif arg =~# '^:/.'
1779 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1781 return 'echoerr v:errmsg'
1784 let file = s:buffer().expand(arg)
1786 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1787 let file = file.s:buffer().path(':')
1790 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1793 let spec = s:repo().translate(file)
1794 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1795 let restore = s:diff_restore()
1796 if exists('+cursorbind')
1799 let w:fugitive_diff_restore = restore
1800 if s:buffer().compare_age(commit) < 0
1801 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1803 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1805 let &l:readonly = &l:readonly
1807 let w:fugitive_diff_restore = restore
1809 if getwinvar('#', '&diff')
1812 call feedkeys(winnr."\<C-W>w", 'n')
1817 return 'echoerr v:errmsg'
1821 " Section: Gmove, Gremove
1823 function! s:Move(force,destination) abort
1824 if a:destination =~# '^/'
1825 let destination = a:destination[1:-1]
1827 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1828 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1829 let destination = destination[strlen(s:repo().tree('')):-1]
1832 if isdirectory(s:buffer().spec())
1833 " Work around Vim parser idiosyncrasy
1834 let discarded = s:buffer().setvar('&swapfile',0)
1836 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1838 let v:errmsg = 'fugitive: '.message
1839 return 'echoerr v:errmsg'
1841 let destination = s:repo().tree(destination)
1842 if isdirectory(destination)
1843 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1845 call fugitive#reload_status()
1846 if s:buffer().commit() == ''
1847 if isdirectory(destination)
1848 return 'keepalt edit '.s:fnameescape(destination)
1850 return 'keepalt saveas! '.s:fnameescape(destination)
1853 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1857 function! s:MoveComplete(A,L,P) abort
1859 return s:repo().superglob(a:A)
1861 let matches = split(glob(a:A.'*'),"\n")
1862 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1867 function! s:Remove(force) abort
1868 if s:buffer().commit() ==# ''
1870 elseif s:buffer().commit() ==# '0'
1871 let cmd = ['rm','--cached']
1873 let v:errmsg = 'fugitive: rm not supported here'
1874 return 'echoerr v:errmsg'
1877 let cmd += ['--force']
1879 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1881 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1882 return 'echoerr '.string(v:errmsg)
1884 call fugitive#reload_status()
1885 return 'edit'.(a:force ? '!' : '')
1889 augroup fugitive_remove
1891 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1892 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1893 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1899 augroup fugitive_blame
1901 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1902 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1903 autocmd Syntax fugitiveblame call s:BlameSyntax()
1904 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
1905 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
1908 function! s:linechars(pattern) abort
1909 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1910 if exists('*synconcealed') && &conceallevel > 1
1911 for col in range(1, chars)
1912 let chars -= synconcealed(line('.'), col)[0]
1918 function! s:Blame(bang,line1,line2,count,args) abort
1919 if exists('b:fugitive_blamed_bufnr')
1923 if s:buffer().path() == ''
1924 call s:throw('file or blob required')
1926 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1927 call s:throw('unsupported option')
1929 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1930 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1931 if s:buffer().commit() =~# '\D\|..'
1932 let cmd += [s:buffer().commit()]
1934 let cmd += ['--contents', '-']
1936 let cmd += ['--', s:buffer().path()]
1937 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1939 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1942 execute cd.'`=s:repo().tree()`'
1945 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1947 let error = resolve(tempname())
1948 let temp = error.'.fugitiveblame'
1950 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1952 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1959 call s:throw(join(readfile(error),"\n"))
1961 for winnr in range(winnr('$'),1,-1)
1962 call setwinvar(winnr, '&scrollbind', 0)
1963 if exists('+cursorbind')
1964 call setwinvar(winnr, '&cursorbind', 0)
1966 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1967 execute winbufnr(winnr).'bdelete'
1970 let bufnr = bufnr('')
1971 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1972 if exists('+cursorbind')
1973 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
1976 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1979 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1981 setlocal scrollbind nowrap nofoldenable
1982 if exists('+cursorbind')
1985 let top = line('w0') + &scrolloff
1986 let current = line('.')
1987 let s:temp_files[s:cpath(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1988 exe 'keepalt leftabove vsplit '.temp
1989 let b:fugitive_blamed_bufnr = bufnr
1990 let w:fugitive_leave = restore
1991 let b:fugitive_blame_arguments = join(a:args,' ')
1995 if exists('+cursorbind')
1998 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1999 if exists('+concealcursor')
2000 setlocal concealcursor=nc conceallevel=2
2002 if exists('+relativenumber')
2003 setlocal norelativenumber
2005 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2006 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2007 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
2008 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2009 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>
2010 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2011 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
2012 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2013 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2014 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2015 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2016 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2017 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2018 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2019 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2030 return 'echoerr v:errmsg'
2034 function! s:BlameCommit(cmd) abort
2035 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
2036 if cmd =~# '^echoerr'
2039 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2040 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2042 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2045 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2047 let head = line('.')
2048 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2049 let top = +matchstr(getline('.'),' +\zs\d\+')
2050 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2051 if lnum >= top && lnum <= top + len
2052 let offset = lnum - top
2060 while offset > 0 && line('.') < line('$')
2062 if getline('.') =~# '^[ +]'
2075 function! s:BlameJump(suffix) abort
2076 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2077 if commit =~# '^0\+$'
2080 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2081 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2083 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2085 let args = b:fugitive_blame_arguments
2086 let offset = line('.') - line('w0')
2087 let bufnr = bufnr('%')
2088 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2090 exe winnr.'wincmd w'
2092 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2097 if exists(':Gblame')
2098 execute 'Gblame '.args
2100 let delta = line('.') - line('w0') - offset
2102 execute 'normal! '.delta."\<C-E>"
2104 execute 'normal! '.(-delta)."\<C-Y>"
2111 let s:hash_colors = {}
2113 function! s:BlameSyntax() abort
2114 let b:current_syntax = 'fugitiveblame'
2115 let conceal = has('conceal') ? ' conceal' : ''
2116 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2117 syn match FugitiveblameBoundary "^\^"
2118 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2119 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2120 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2121 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2122 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2123 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2124 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2125 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2126 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2127 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2128 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2129 hi def link FugitiveblameBoundary Keyword
2130 hi def link FugitiveblameHash Identifier
2131 hi def link FugitiveblameUncommitted Ignore
2132 hi def link FugitiveblameTime PreProc
2133 hi def link FugitiveblameLineNumber Number
2134 hi def link FugitiveblameOriginalFile String
2135 hi def link FugitiveblameOriginalLineNumber Float
2136 hi def link FugitiveblameShort FugitiveblameDelimiter
2137 hi def link FugitiveblameDelimiter Delimiter
2138 hi def link FugitiveblameNotCommittedYet Comment
2140 for lnum in range(1, line('$'))
2141 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2142 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2146 if &t_Co > 16 && exists('g:CSApprox_loaded')
2147 \ && empty(get(s:hash_colors, hash))
2148 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2149 let color = csapprox#per_component#Approximate(r, g, b)
2150 if color == 16 && &background ==# 'dark'
2153 let s:hash_colors[hash] = ' ctermfg='.color
2155 let s:hash_colors[hash] = ''
2157 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2159 call s:RehighlightBlame()
2162 function! s:RehighlightBlame() abort
2163 for [hash, cterm] in items(s:hash_colors)
2164 if !empty(cterm) || has('gui_running')
2165 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2167 exe 'hi link FugitiveblameHash'.hash.' Identifier'
2174 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2176 function! s:Browse(bang,line1,count,...) abort
2178 let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
2180 let expanded = s:buffer().rev()
2182 let expanded = s:buffer().path('/')
2184 let expanded = s:buffer().expand(rev)
2186 let full = s:repo().translate(expanded)
2188 if full =~# '^fugitive://'
2189 let commit = matchstr(full,'://.*//\zs\w\+')
2190 let path = matchstr(full,'://.*//\w\+\zs/.*')
2192 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2196 let path = path[1:-1]
2197 elseif s:repo().bare()
2198 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2201 let path = full[strlen(s:repo().tree())+1:-1]
2202 if path =~# '^\.git/'
2204 elseif isdirectory(full)
2210 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2211 let body = readfile(s:repo().dir(path[5:-1]))[0]
2212 if body =~# '^\x\{40\}$'
2216 elseif body =~# '^ref: refs/'
2217 let path = '.git/' . matchstr(body,'ref: \zs.*')
2221 if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
2222 let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
2223 elseif path =~# '^\.git/refs/remotes/.'
2224 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
2226 let remote = 'origin'
2227 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
2228 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
2229 let branch = s:sub(path,'^\.git/refs/\w+/','')
2231 if filereadable(s:repo().dir('refs/remotes/'.branch))
2232 let remote = matchstr(branch,'[^/]\+')
2233 let rev = rev[strlen(remote)+1:-1]
2236 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
2239 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
2240 if remote =~# '^\.\=$'
2241 let remote = 'origin'
2242 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
2243 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
2249 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
2254 for Handler in g:fugitive_browse_handlers
2255 let url = call(Handler, [{
2262 \ 'line1': a:count > 0 ? a:line1 : 0,
2263 \ 'line2': a:count > 0 ? a:count : 0}])
2270 call s:throw("Instaweb failed to start and '".remote."' is not a supported remote")
2277 return 'echomsg '.string(url)
2278 elseif exists(':Browse') == 2
2279 return 'echomsg '.string(url).'|Browse '.url
2281 if !exists('g:loaded_netrw')
2282 runtime! autoload/netrw.vim
2284 if exists('*netrw#BrowseX')
2285 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
2287 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2291 return 'echoerr v:errmsg'
2295 function! s:github_url(opts, ...) abort
2296 if a:0 || type(a:opts) != type({})
2299 let domain_pattern = 'github\.com'
2300 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2301 for domain in domains
2302 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2304 let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2308 let path = a:opts.path
2309 if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
2310 let root = 'http://' . s:sub(repo,':','/')
2312 let root = 'https://' . s:sub(repo,':','/')
2314 if path =~# '^\.git/refs/heads/'
2315 let branch = a:opts.repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
2317 return root . '/commits/' . path[16:-1]
2319 return root . '/commits/' . branch
2321 elseif path =~# '^\.git/refs/tags/'
2322 return root . '/releases/tag/' . matchstr(path,'[^/]\+$')
2323 elseif path =~# '^\.git/refs/.'
2324 return root . '/commits/' . matchstr(path,'[^/]\+$')
2325 elseif path =~# '.git/\%(config$\|hooks\>\)'
2326 return root . '/admin'
2327 elseif path =~# '^\.git\>'
2330 if a:opts.revision =~# '^[[:alnum:]._-]\+:'
2331 let commit = matchstr(a:opts.revision,'^[^:]*')
2332 elseif a:opts.commit =~# '^\d\=$'
2333 let local = matchstr(a:opts.repo.head_ref(),'\<refs/heads/\zs.*')
2334 let commit = a:opts.repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2339 let commit = a:opts.commit
2341 if a:opts.type == 'tree'
2342 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2343 elseif a:opts.type == 'blob'
2344 let url = root . '/blob/' . commit . '/' . path
2345 if get(a:opts, 'line2') && a:opts.line1 == a:opts.line2
2346 let url .= '#L' . a:opts.line1
2347 elseif get(a:opts, 'line2')
2348 let url .= '#L' . a:opts.line1 . '-L' . a:opts.line2
2351 let url = root . '/commit/' . commit
2356 function! s:instaweb_url(opts) abort
2357 let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2358 if output =~# 'http://'
2359 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2363 if a:opts.path =~# '^\.git/refs/.'
2364 return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2365 elseif a:opts.path =~# '^\.git\>'
2369 if a:opts.commit =~# '^\x\{40\}$'
2370 if a:opts.type ==# 'commit'
2371 let url .= ';a=commit'
2373 let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2375 if a:opts.type ==# 'blob'
2376 let tmp = tempname()
2377 silent execute 'write !'.a:opts.repo.git_command('hash-object','-w','--stdin').' > '.tmp
2378 let url .= ';h=' . readfile(tmp)[0]
2381 let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2383 call s:throw('fugitive: cannot browse uncommitted file')
2386 let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2388 if a:opts.path !=# ''
2389 let url .= ';f=' . a:opts.path
2391 if get(a:opts, 'line1')
2392 let url .= '#l' . a:opts.line1
2397 if !exists('g:fugitive_browse_handlers')
2398 let g:fugitive_browse_handlers = []
2401 call extend(g:fugitive_browse_handlers,
2402 \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2404 " Section: File access
2406 function! s:ReplaceCmd(cmd,...) abort
2407 let fn = expand('%:p')
2408 let tmp = tempname()
2413 let old_index = $GIT_INDEX_FILE
2414 let $GIT_INDEX_FILE = a:1
2416 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2419 let redir = ' > '.tmp
2420 if &shellpipe =~ '2>&1'
2421 let redir .= ' 2>&1'
2424 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2425 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').redir.'"')
2426 elseif &shell =~# 'fish'
2427 call system(' begin;'.prefix.a:cmd.redir.';end ')
2429 call system(' ('.prefix.a:cmd.redir.') ')
2432 if exists('old_index')
2433 let $GIT_INDEX_FILE = old_index
2436 silent exe 'keepalt file '.tmp
2441 silent exe 'keepalt file '.s:fnameescape(fn)
2442 catch /^Vim\%((\a\+)\)\=:E302/
2445 if fnamemodify(bufname('$'), ':p') ==# tmp
2446 silent execute 'bwipeout '.bufnr('$')
2448 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2452 function! s:BufReadIndex() abort
2453 if !exists('b:fugitive_display_format')
2454 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2456 let b:fugitive_display_format = b:fugitive_display_format % 2
2457 let b:fugitive_type = 'index'
2459 let b:git_dir = s:repo().dir()
2460 setlocal noro ma nomodeline
2461 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2464 let index = expand('%:p')
2466 if b:fugitive_display_format
2467 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2470 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2472 if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2473 let cmd = s:repo().git_command('status')
2475 let cmd = s:repo().git_command(
2476 \ '-c', 'status.displayCommentPrefix=true',
2477 \ '-c', 'color.status=false',
2478 \ '-c', 'status.short=false',
2482 execute cd.'`=s:repo().tree()`'
2483 call s:ReplaceCmd(cmd, index)
2488 set foldtext=fugitive#foldtext()
2490 setlocal ro noma nomod noswapfile
2491 if &bufhidden ==# ''
2492 setlocal bufhidden=delete
2497 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2498 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2499 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2500 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2501 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2502 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2503 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2504 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2505 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2506 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2507 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2508 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2509 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2510 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2511 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2512 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2513 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2514 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2515 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2516 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2517 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2518 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2519 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2520 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2521 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2522 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2524 return 'echoerr v:errmsg'
2528 function! s:FileRead() abort
2530 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2531 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2532 let hash = repo.rev_parse(path)
2536 let type = repo.git_chomp('cat-file','-t',hash)
2538 " TODO: use count, if possible
2539 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2541 return 'echoerr v:errmsg'
2545 function! s:BufReadIndexFile() abort
2547 let b:fugitive_type = 'blob'
2548 let b:git_dir = s:repo().dir()
2550 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2552 if &bufhidden ==# ''
2553 setlocal bufhidden=delete
2558 catch /^fugitive: rev-parse/
2559 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2562 return 'echoerr v:errmsg'
2566 function! s:BufWriteIndexFile() abort
2567 let tmp = tempname()
2569 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2570 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2571 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2572 let sha1 = readfile(tmp)[0]
2573 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2575 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2577 let info = old_mode.' '.sha1.' '.stage."\t".path
2578 call writefile([info],tmp)
2580 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2582 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2584 if v:shell_error == 0
2586 if exists('#BufWritePost')
2587 execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2589 call fugitive#reload_status()
2592 return 'echoerr '.string('fugitive: '.error)
2599 function! s:BufReadObject() abort
2602 let b:git_dir = s:repo().dir()
2603 let hash = s:buffer().sha1()
2604 if !exists("b:fugitive_type")
2605 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2607 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2608 return "echoerr 'fugitive: unrecognized git type'"
2610 let firstline = getline('.')
2611 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2612 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2615 if b:fugitive_type !=# 'blob'
2619 let pos = getpos('.')
2620 silent keepjumps %delete_
2624 if b:fugitive_type ==# 'tree'
2625 let b:fugitive_display_format = b:fugitive_display_format % 2
2626 if b:fugitive_display_format
2627 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2629 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2631 elseif b:fugitive_type ==# 'tag'
2632 let b:fugitive_display_format = b:fugitive_display_format % 2
2633 if b:fugitive_display_format
2634 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2636 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2638 elseif b:fugitive_type ==# 'commit'
2639 let b:fugitive_display_format = b:fugitive_display_format % 2
2640 if b:fugitive_display_format
2641 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2643 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))
2644 keepjumps call search('^parent ')
2645 if getline('.') ==# 'parent '
2646 silent keepjumps delete_
2648 silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2650 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2652 silent keepjumps delete_
2656 elseif b:fugitive_type ==# 'blob'
2657 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2661 keepjumps call setpos('.',pos)
2662 setlocal ro noma nomod noswapfile
2663 if &bufhidden ==# ''
2664 setlocal bufhidden=delete
2666 if b:fugitive_type !=# 'blob'
2667 setlocal filetype=git foldmethod=syntax
2668 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2669 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2677 return 'echoerr v:errmsg'
2681 augroup fugitive_files
2683 autocmd BufReadCmd index{,.lock}
2684 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2685 \ exe s:BufReadIndex() |
2686 \ elseif filereadable(expand('<amatch>')) |
2690 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2691 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2692 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2693 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2694 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2695 autocmd FileType git
2696 \ if exists('b:git_dir') |
2697 \ call s:JumpInit() |
2699 autocmd FileType git,gitcommit,gitrebase
2700 \ if exists('b:git_dir') |
2705 " Section: Temp files
2707 if !exists('s:temp_files')
2708 let s:temp_files = {}
2711 augroup fugitive_temp
2713 autocmd BufNewFile,BufReadPost *
2714 \ if has_key(s:temp_files,s:cpath(expand('<afile>:p'))) |
2715 \ let b:git_dir = s:temp_files[s:cpath(expand('<afile>:p'))].dir |
2716 \ let b:git_type = 'temp' |
2717 \ let b:git_args = s:temp_files[s:cpath(expand('<afile>:p'))].args |
2718 \ call fugitive#detect(expand('<afile>:p')) |
2719 \ setlocal bufhidden=delete nobuflisted |
2720 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2724 " Section: Go to file
2726 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
2727 function! s:GFInit(...) abort
2728 cnoremap <buffer> <expr> <Plug><cfile> fugitive#cfile()
2729 if !exists('g:fugitive_no_maps') && empty(mapcheck('gf', 'n'))
2730 nmap <buffer> <silent> gf <SID>:find <Plug><cfile><CR>
2731 nmap <buffer> <silent> <C-W>f <SID>:sfind <Plug><cfile><CR>
2732 nmap <buffer> <silent> <C-W><C-F> <SID>:sfind <Plug><cfile><CR>
2733 nmap <buffer> <silent> <C-W>gf <SID>:tabfind <Plug><cfile><CR>
2737 function! s:JumpInit(...) abort
2738 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2740 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2741 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2742 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2743 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>
2744 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2745 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2746 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2747 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2748 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2749 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2750 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2751 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2752 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2756 function! s:cfile() abort
2758 let buffer = s:buffer()
2759 let myhash = buffer.sha1()
2760 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2761 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2764 if buffer.type('tree')
2765 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2766 if showtree && line('.') > 2
2767 return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$','')]
2768 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2769 return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
2772 elseif buffer.type('blob')
2773 let ref = expand("<cfile>")
2775 let sha1 = buffer.repo().rev_parse(ref)
2787 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2788 let ref = matchstr(getline('.'),'\x\{40\}')
2789 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2792 elseif getline('.') =~# '^#\trenamed:.* -> '
2793 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2795 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2796 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2798 elseif getline('.') =~# '^#\t.'
2799 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2801 elseif getline('.') =~# ': needs merge$'
2802 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2803 return [file, 'Gdiff!']
2805 elseif getline('.') ==# '# Not currently on any branch.'
2807 elseif getline('.') =~# '^# On branch '
2808 let file = 'refs/heads/'.getline('.')[12:]
2810 elseif getline('.') =~# "^# Your branch .*'"
2811 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2815 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2817 if getline('.') =~# '^ref: '
2818 let ref = strpart(getline('.'),5)
2820 elseif getline('.') =~# '^commit \x\{40\}\>'
2821 let ref = matchstr(getline('.'),'\x\{40\}')
2824 elseif getline('.') =~# '^parent \x\{40\}\>'
2825 let ref = matchstr(getline('.'),'\x\{40\}')
2826 let line = line('.')
2828 while getline(line) =~# '^parent '
2834 elseif getline('.') =~ '^tree \x\{40\}$'
2835 let ref = matchstr(getline('.'),'\x\{40\}')
2836 if s:repo().rev_parse(myhash.':') == ref
2837 let ref = myhash.':'
2841 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2842 let ref = matchstr(getline('.'),'\x\{40\}')
2843 let type = matchstr(getline(line('.')+1),'type \zs.*')
2845 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2846 let ref = buffer.rev()
2848 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2849 let ref = matchstr(getline('.'),'\x\{40\}')
2850 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2852 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2853 let ref = getline('.')[4:]
2855 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2856 let type = getline('.')[0]
2857 let lnum = line('.') - 1
2859 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2860 if getline(lnum) =~# '^[ '.type.']'
2865 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2866 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2867 let dcmds = [offset, 'normal!zv']
2869 elseif getline('.') =~# '^rename from '
2870 let ref = 'a/'.getline('.')[12:]
2871 elseif getline('.') =~# '^rename to '
2872 let ref = 'b/'.getline('.')[10:]
2874 elseif getline('.') =~# '^@@ -\d\+,\d\+ +\d\+,'
2875 let diff = getline(search('^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)', 'bcnW'))
2876 let offset = matchstr(getline('.'), '+\zs\d\+')
2878 let dref = matchstr(diff, '\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2879 let ref = matchstr(diff, '\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2880 let dcmd = 'Gdiff! +'.offset
2882 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2883 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2884 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2887 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2888 let line = getline(line('.')-1)
2889 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2890 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2893 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2894 let ref = getline('.')
2896 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
2897 return [expand('<cword>')]
2904 let ref = s:sub(ref,'^a/','HEAD:')
2905 let ref = s:sub(ref,'^b/',':0:')
2907 let dref = s:sub(dref,'^a/','HEAD:')
2910 let ref = s:sub(ref,'^a/',myhash.'^:')
2911 let ref = s:sub(ref,'^b/',myhash.':')
2913 let dref = s:sub(dref,'^a/',myhash.'^:')
2917 if ref ==# '/dev/null'
2919 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2923 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
2925 return [ref] + dcmds
2933 function! s:GF(mode) abort
2935 let results = s:cfile()
2937 return 'echoerr v:errmsg'
2940 return s:Edit(a:mode, 0, results[0]).join(map(results[1:-1], '"|".v:val'), '')
2946 function! fugitive#cfile() abort
2948 let results = s:cfile()
2950 return expand('<cfile>')
2951 elseif len(results) > 1
2952 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
2954 return pre . s:fnameescape(fugitive#repo().translate(results[0]))
2957 " Section: Statusline
2959 function! s:repo_head_ref() dict abort
2960 if !filereadable(self.dir('HEAD'))
2963 return readfile(self.dir('HEAD'))[0]
2966 call s:add_methods('repo',['head_ref'])
2968 function! fugitive#statusline(...) abort
2969 if !exists('b:git_dir')
2973 if s:buffer().commit() != ''
2974 let status .= ':' . s:buffer().commit()[0:7]
2976 let status .= '('.fugitive#head(7).')'
2977 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2978 return ',GIT'.status
2980 return '[Git'.status.']'
2984 function! fugitive#head(...) abort
2985 if !exists('b:git_dir')
2989 return s:repo().head(a:0 ? a:1 : 0)
2992 augroup fugitive_statusline
2994 autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
2999 function! fugitive#foldtext() abort
3000 if &foldmethod !=# 'syntax'
3002 elseif getline(v:foldstart) =~# '^diff '
3003 let [add, remove] = [-1, -1]
3005 for lnum in range(v:foldstart, v:foldend)
3006 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
3007 let filename = getline(lnum)[6:-1]
3009 if getline(lnum) =~# '^+'
3011 elseif getline(lnum) =~# '^-'
3013 elseif getline(lnum) =~# '^Binary '
3018 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
3021 let filename = getline(v:foldstart)[5:-1]
3024 return 'Binary: '.filename
3026 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3028 elseif getline(v:foldstart) =~# '^# .*:$'
3029 let lines = getline(v:foldstart, v:foldend)
3030 call filter(lines, 'v:val =~# "^#\t"')
3031 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
3032 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
3033 return getline(v:foldstart).' '.join(lines, ', ')
3038 augroup fugitive_foldtext
3040 autocmd User Fugitive
3041 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3042 \ set foldtext=fugitive#foldtext() |