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 if fugitive#is_git_dir($GIT_DIR)
149 " Ensure that we've cached the worktree
150 call s:configured_tree($GIT_DIR)
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 cnoremap <buffer> <expr> <C-R><C-G> fnameescape(<SID>recall())
197 nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
198 let buffer = fugitive#buffer()
199 if expand('%:p') =~# '//'
200 call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
202 if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
203 if filereadable(b:git_dir.'/tags')
204 call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
206 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
207 call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
211 let [save_mls, &modelines] = [&mls, 0]
212 doautocmd User Fugitive
221 autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('<amatch>:p'))
222 autocmd FileType netrw call fugitive#detect(expand('%:p'))
223 autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTreeRoot.path.str())
224 autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
225 autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
226 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
229 " Section: Repository
231 let s:repo_prototype = {}
233 let s:worktree_for_dir = {}
234 let s:dir_for_worktree = {}
236 function! s:repo(...) abort
237 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
239 if has_key(s:repos, dir)
240 let repo = get(s:repos, dir)
242 let repo = {'git_dir': dir}
243 let s:repos[dir] = repo
245 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
247 call s:throw('not a git repository: '.expand('%:p'))
250 function! fugitive#repo(...) abort
251 return call('s:repo', a:000)
254 function! s:repo_dir(...) dict abort
255 return join([self.git_dir]+a:000,'/')
258 function! s:configured_tree(git_dir) abort
259 if !has_key(s:worktree_for_dir, a:git_dir)
260 let s:worktree_for_dir[a:git_dir] = ''
261 let config_file = a:git_dir . '/config'
262 if filereadable(config_file)
263 let config = readfile(config_file,'',10)
264 call filter(config,'v:val =~# "^\\s*worktree *="')
266 let s:worktree_for_dir[a:git_dir] = matchstr(config[0], '= *\zs.*')
267 let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
271 if s:worktree_for_dir[a:git_dir] =~# '^\.'
272 return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
274 return s:worktree_for_dir[a:git_dir]
278 function! s:repo_tree(...) dict abort
279 if self.dir() =~# '/\.git$'
280 let dir = self.dir()[0:-6]
282 let dir = s:configured_tree(self.git_dir)
285 call s:throw('no work tree')
287 return join([dir]+a:000,'/')
291 function! s:repo_bare() dict abort
292 if self.dir() =~# '/\.git$'
295 return s:configured_tree(self.git_dir) ==# ''
299 function! s:repo_translate(spec) dict abort
300 if a:spec ==# '.' || a:spec ==# '/.'
301 return self.bare() ? self.dir() : self.tree()
302 elseif a:spec =~# '^/\=\.git$' && self.bare()
304 elseif a:spec =~# '^/\=\.git/'
305 return self.dir(s:sub(a:spec, '^/=\.git/', ''))
306 elseif a:spec =~# '^/'
307 return self.tree().a:spec
308 elseif a:spec =~# '^:[0-3]:'
309 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
310 elseif a:spec ==# ':'
311 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
312 return fnamemodify($GIT_INDEX_FILE,':p')
314 return self.dir('index')
316 elseif a:spec =~# '^:/'
317 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
318 return 'fugitive://'.self.dir().'//'.ref
319 elseif a:spec =~# '^:'
320 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
321 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
322 return self.dir(a:spec)
323 elseif filereadable(self.dir('refs/'.a:spec))
324 return self.dir('refs/'.a:spec)
325 elseif filereadable(self.dir('refs/tags/'.a:spec))
326 return self.dir('refs/tags/'.a:spec)
327 elseif filereadable(self.dir('refs/heads/'.a:spec))
328 return self.dir('refs/heads/'.a:spec)
329 elseif filereadable(self.dir('refs/remotes/'.a:spec))
330 return self.dir('refs/remotes/'.a:spec)
331 elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
332 return self.dir('refs/remotes/'.a:spec,'/HEAD')
335 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
336 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
337 return 'fugitive://'.self.dir().'//'.ref.path
339 return self.tree(a:spec)
344 function! s:repo_head(...) dict abort
345 let head = s:repo().head_ref()
348 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
349 elseif head =~# '^\x\{40\}$'
350 " truncate hash to a:1 characters if we're in detached head mode
351 let len = a:0 ? a:1 : 0
352 let branch = len ? head[0:len-1] : ''
360 call s:add_methods('repo',['dir','tree','bare','translate','head'])
362 function! s:repo_git_command(...) dict abort
363 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
364 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
367 function! s:repo_git_chomp(...) dict abort
368 return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
371 function! s:repo_git_chomp_in_tree(...) dict abort
372 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
375 execute cd.'`=s:repo().tree()`'
376 return call(s:repo().git_chomp, a:000, s:repo())
382 function! s:repo_rev_parse(rev) dict abort
383 let hash = self.git_chomp('rev-parse','--verify',a:rev)
384 if hash =~ '\<\x\{40\}$'
385 return matchstr(hash,'\<\x\{40\}$')
387 call s:throw('rev-parse '.a:rev.': '.hash)
390 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
392 function! s:repo_dirglob(base) dict abort
393 let base = s:sub(a:base,'^/','')
394 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
395 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
399 function! s:repo_superglob(base) dict abort
400 if a:base =~# '^/' || a:base !~# ':'
403 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
404 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
405 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
409 let base = s:sub(a:base,'^/','')
410 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
411 call map(matches,'s:shellslash(v:val)')
412 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
413 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
414 let results += matches
418 elseif a:base =~# '^:'
419 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
420 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
421 if a:base !~# '^:[0-3]\%(:\|$\)'
422 call filter(entries,'v:val[1] == "0"')
423 call map(entries,'v:val[2:-1]')
425 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
429 let tree = matchstr(a:base,'.*[:/]')
430 let entries = split(self.git_chomp('ls-tree',tree),"\n")
431 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
432 call map(entries,'tree.s:sub(v:val,".*\t","")')
433 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
437 call s:add_methods('repo',['dirglob','superglob'])
439 function! s:repo_config(conf) dict abort
440 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
443 function! s:repo_user() dict abort
444 let username = s:repo().config('user.name')
445 let useremail = s:repo().config('user.email')
446 return username.' <'.useremail.'>'
449 function! s:repo_aliases() dict abort
450 if !has_key(self,'_aliases')
451 let self._aliases = {}
452 for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
453 let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
459 call s:add_methods('repo',['config', 'user', 'aliases'])
461 function! s:repo_keywordprg() dict abort
462 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
463 if has('gui_running') && !has('win32')
464 return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
466 return g:fugitive_git_executable . args . ' show'
470 call s:add_methods('repo',['keywordprg'])
474 let s:buffer_prototype = {}
476 function! s:buffer(...) abort
477 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
478 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
479 if buffer.getvar('git_dir') !=# ''
482 call s:throw('not a git repository: '.expand('%:p'))
485 function! fugitive#buffer(...) abort
486 return s:buffer(a:0 ? a:1 : '%')
489 function! s:buffer_getvar(var) dict abort
490 return getbufvar(self['#'],a:var)
493 function! s:buffer_setvar(var,value) dict abort
494 return setbufvar(self['#'],a:var,a:value)
497 function! s:buffer_getline(lnum) dict abort
498 return get(getbufline(self['#'], a:lnum), 0, '')
501 function! s:buffer_repo() dict abort
502 return s:repo(self.getvar('git_dir'))
505 function! s:buffer_type(...) dict abort
506 if self.getvar('fugitive_type') != ''
507 let type = self.getvar('fugitive_type')
508 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
510 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
512 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
514 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
516 elseif isdirectory(self.spec())
517 let type = 'directory'
518 elseif self.spec() == ''
524 return !empty(filter(copy(a:000),'v:val ==# type'))
532 function! s:buffer_spec() dict abort
533 let bufname = bufname(self['#'])
535 for i in split(bufname,'[^:]\zs\\')
536 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
538 return s:shellslash(fnamemodify(retval,':p'))
543 function! s:buffer_spec() dict abort
544 let bufname = bufname(self['#'])
545 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
550 function! s:buffer_name() dict abort
554 function! s:buffer_commit() dict abort
555 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
558 function! s:buffer_path(...) dict abort
559 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
561 let rev = s:sub(rev,'\w*','')
562 elseif self.spec()[0 : len(self.repo().dir())] ==# self.repo().dir() . '/'
563 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
564 elseif !self.repo().bare() && self.spec()[0 : len(self.repo().tree())] ==# self.repo().tree() . '/'
565 let rev = self.spec()[strlen(self.repo().tree()) : -1]
567 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
570 function! s:buffer_rev() dict abort
571 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
573 return ':'.rev[0].':'.rev[2:-1]
575 return s:sub(rev,'/',':')
576 elseif self.spec() =~ '\.git/index$'
578 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
579 return self.spec()[strlen(self.repo().dir())+1 : -1]
581 return self.path('/')
585 function! s:buffer_sha1() dict abort
586 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
587 return self.repo().rev_parse(self.rev())
593 function! s:buffer_expand(rev) dict abort
594 if a:rev =~# '^:[0-3]$'
595 let file = a:rev.self.path(':')
596 elseif a:rev =~# '^[-:]/$'
597 let file = '/'.self.path()
598 elseif a:rev =~# '^-'
599 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
600 elseif a:rev =~# '^@{'
601 let file = 'HEAD'.a:rev.self.path(':')
602 elseif a:rev =~# '^[~^]'
603 let commit = s:sub(self.commit(),'^\d=$','HEAD')
604 let file = commit.a:rev.self.path(':')
608 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
611 function! s:buffer_containing_commit() dict abort
612 if self.commit() =~# '^\d$'
614 elseif self.commit() =~# '.'
621 function! s:buffer_up(...) dict abort
623 let c = a:0 ? a:1 : 1
629 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
631 elseif rev =~# '^/\|:.*/'
632 let rev = s:sub(rev, '.*\zs/.*', '')
634 let rev = matchstr(rev, '^[^:]*:')
645 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
649 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
651 function! s:ExecuteInTree(cmd) abort
652 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
655 execute cd.'`=s:repo().tree()`'
662 function! s:Git(bang, args) abort
664 return s:Edit('edit', 1, a:args)
666 let git = g:fugitive_git_executable
667 if has('gui_running') && !has('win32')
668 let git .= ' --no-pager'
670 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
671 call s:ExecuteInTree('!'.git.' '.args)
672 call fugitive#reload_status()
673 return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
676 function! fugitive#git_commands() abort
677 if !exists('s:exec_path')
678 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
680 return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
683 function! s:GitComplete(A, L, P) abort
684 if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
685 let cmds = fugitive#git_commands()
686 return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
688 return s:repo().superglob(a:A)
694 function! s:DirComplete(A,L,P) abort
695 let matches = s:repo().dirglob(a:A)
699 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>)`")
700 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>)`")
704 call s:command("-bar Gstatus :execute s:Status()")
705 augroup fugitive_status
708 autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
712 function! s:Status() abort
716 setlocal foldmethod=syntax foldlevel=1
717 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
719 return 'echoerr v:errmsg'
724 function! fugitive#reload_status() abort
725 if exists('s:reloading_status')
729 let s:reloading_status = 1
730 let mytab = tabpagenr()
731 for tab in [mytab] + range(1,tabpagenr('$'))
732 for winnr in range(1,tabpagewinnr(tab,'$'))
733 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
734 execute 'tabnext '.tab
736 execute winnr.'wincmd w'
741 call s:BufReadIndex()
744 if exists('restorewinnr')
747 execute 'tabnext '.mytab
753 unlet! s:reloading_status
757 function! s:stage_info(lnum) abort
758 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
760 if has('multi_byte_encoding')
761 let colon = '\%(:\|\%uff1a\)'
765 while lnum && getline(lnum) !~# colon.'$'
770 elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
771 return [matchstr(filename, colon.' *\zs.*'), 'staged']
772 elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
773 return [filename, 'untracked']
774 elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
775 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
776 elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
777 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
779 return ['', 'unknown']
783 function! s:StageNext(count) abort
784 for i in range(a:count)
785 call search('^#\t.*','W')
790 function! s:StagePrevious(count) abort
791 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
792 return 'CtrlP '.fnameescape(s:repo().tree())
794 for i in range(a:count)
795 call search('^#\t.*','Wbe')
801 function! s:StageReloadSeek(target,lnum1,lnum2) abort
803 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
804 if f !=# '' | let jump = f | endif
805 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
806 if f !=# '' | let jump = f | endif
810 call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
813 function! s:StageUndo() abort
814 let [filename, section] = s:stage_info(line('.'))
819 let hash = repo.git_chomp('hash-object', '-w', filename)
821 if section ==# 'untracked'
822 call delete(s:repo().tree(filename))
823 elseif section ==# 'unstaged'
824 call repo.git_chomp_in_tree('checkout', '--', filename)
826 call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
828 call s:StageReloadSeek(filename, line('.'), line('.'))
830 return 'checktime|redraw|echomsg ' .
831 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
835 function! s:StageDiff(diff) abort
836 let [filename, section] = s:stage_info(line('.'))
837 if filename ==# '' && section ==# 'staged'
838 return 'Git! diff --no-ext-diff --cached'
839 elseif filename ==# ''
840 return 'Git! diff --no-ext-diff'
841 elseif filename =~# ' -> '
842 let [old, new] = split(filename,' -> ')
843 execute 'Gedit '.s:fnameescape(':0:'.new)
844 return a:diff.' HEAD:'.s:fnameescape(old)
845 elseif section ==# 'staged'
846 execute 'Gedit '.s:fnameescape(':0:'.filename)
849 execute 'Gedit '.s:fnameescape('/'.filename)
854 function! s:StageDiffEdit() abort
855 let [filename, section] = s:stage_info(line('.'))
856 let arg = (filename ==# '' ? '.' : filename)
857 if section ==# 'staged'
858 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
859 elseif section ==# 'untracked'
861 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
865 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
866 call search('^# .*:$','W')
869 call s:StageReloadSeek(arg,line('.'),line('.'))
873 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
877 function! s:StageToggle(lnum1,lnum2) abort
878 if a:lnum1 == 1 && a:lnum2 == 1
879 return 'Gedit /.git|call search("^index$", "wc")'
883 for lnum in range(a:lnum1,a:lnum2)
884 let [filename, section] = s:stage_info(lnum)
886 if getline('.') =~# '^# .*:$'
887 if section ==# 'staged'
888 call repo.git_chomp_in_tree('reset','-q')
891 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
892 call search('^# .*:$','W')
895 elseif section ==# 'unstaged'
896 call repo.git_chomp_in_tree('add','-u')
899 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
900 call search('^# .*:$','W')
904 call repo.git_chomp_in_tree('add','.')
907 call search('^# .*:$','W')
915 if filename =~ ' -> '
916 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
917 let filename = cmd[-1]
918 elseif section ==# 'staged'
919 let cmd = ['reset','-q','--',filename]
920 elseif getline(lnum) =~# '^#\tdeleted:'
921 let cmd = ['rm','--',filename]
922 elseif getline(lnum) =~# '^#\tmodified:'
923 let cmd = ['add','--',filename]
925 let cmd = ['add','-A','--',filename]
927 if !exists('first_filename')
928 let first_filename = filename
930 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
932 if exists('first_filename')
933 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
935 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
937 return 'echoerr v:errmsg'
942 function! s:StagePatch(lnum1,lnum2) abort
946 for lnum in range(a:lnum1,a:lnum2)
947 let [filename, section] = s:stage_info(lnum)
948 if getline('.') =~# '^# .*:$' && section ==# 'staged'
949 return 'Git reset --patch'
950 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
951 return 'Git add --patch'
952 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
953 return 'Git add -N .'
954 elseif filename ==# ''
957 if !exists('first_filename')
958 let first_filename = filename
961 if filename =~ ' -> '
962 let reset += [split(filename,' -> ')[1]]
963 elseif section ==# 'staged'
964 let reset += [filename]
965 elseif getline(lnum) !~# '^#\tdeleted:'
966 let add += [filename]
971 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
974 execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
976 if exists('first_filename')
980 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
983 return 'echoerr v:errmsg'
990 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
992 function! s:Commit(args, ...) abort
993 let repo = a:0 ? a:1 : s:repo()
994 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
996 let msgfile = repo.dir('COMMIT_EDITMSG')
997 let outfile = tempname()
998 let errorfile = tempname()
1001 execute cd.s:fnameescape(repo.tree())
1004 let old_editor = $GIT_EDITOR
1005 let $GIT_EDITOR = 'false'
1007 let command = 'env GIT_EDITOR=false '
1009 let command .= repo.git_command('commit').' '.a:args
1011 noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1012 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1013 noautocmd execute '!'.command.' 2> '.errorfile
1015 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1020 if !has('gui_running')
1024 if filereadable(outfile)
1025 for line in readfile(outfile)
1031 let errors = readfile(errorfile)
1032 let error = get(errors,-2,get(errors,-1,'!'))
1033 if error =~# 'false''\=\.$'
1035 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1036 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1037 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1038 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1039 let args = '-F '.s:shellesc(msgfile).' '.args
1040 if args !~# '\%(^\| \)--cleanup\>'
1041 let args = '--cleanup=strip '.args
1043 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1044 execute 'keepalt edit '.s:fnameescape(msgfile)
1045 elseif a:args =~# '\%(^\| \)-\%(-verbose\|\w*v\)\>'
1046 execute 'keepalt tabedit '.s:fnameescape(msgfile)
1047 elseif s:buffer().type() ==# 'index'
1048 execute 'keepalt edit '.s:fnameescape(msgfile)
1049 execute (search('^#','n')+1).'wincmd+'
1050 setlocal nopreviewwindow
1052 execute 'keepalt split '.s:fnameescape(msgfile)
1054 let b:fugitive_commit_arguments = args
1055 setlocal bufhidden=wipe filetype=gitcommit
1057 elseif error ==# '!'
1064 return 'echoerr v:errmsg'
1066 if exists('old_editor')
1067 let $GIT_EDITOR = old_editor
1069 call delete(outfile)
1070 call delete(errorfile)
1071 call fugitive#reload_status()
1075 function! s:CommitComplete(A,L,P) abort
1076 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1077 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']
1078 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1080 return s:repo().superglob(a:A)
1084 function! s:FinishCommit() abort
1085 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1087 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1088 return s:Commit(args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1093 " Section: Gmerge, Gpull
1095 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1096 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1097 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1098 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1100 function! s:RevisionComplete(A, L, P) abort
1101 return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1102 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1105 function! s:RemoteComplete(A, L, P) abort
1106 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1108 let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1109 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1110 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1112 let matches = split(s:repo().git_chomp('remote'), "\n")
1114 return join(matches, "\n")
1117 function! fugitive#cwindow() abort
1118 if &buftype == 'quickfix'
1122 if &buftype == 'quickfix'
1128 let s:common_efm = ''
1130 \ . '%+Eusage:%.%#,'
1131 \ . '%+Eerror:%.%#,'
1132 \ . '%+Efatal:%.%#,'
1133 \ . '%-G%.%#%\e[K%.%#,'
1134 \ . '%-G%.%#%\r%.%\+'
1136 function! s:Merge(cmd, bang, args) abort
1137 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1139 let [mp, efm] = [&l:mp, &l:efm]
1140 let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1142 let &l:errorformat = ''
1143 \ . '%-Gerror:%.%#false''.,'
1144 \ . '%-G%.%# ''git commit'' %.%#,'
1145 \ . '%+Emerge:%.%#,'
1146 \ . s:common_efm . ','
1147 \ . '%+ECannot %.%#: You have unstaged changes.,'
1148 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1149 \ . '%+EThere is no tracking information for the current branch.,'
1150 \ . '%+EYou are not currently on a branch. Please specify which,'
1151 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1152 \ . 'CONFLICT (%m): Merge conflict in %f,'
1153 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1154 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1155 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1156 \ . '%+ECONFLICT %.%#,'
1157 \ . '%+EKONFLIKT %.%#,'
1158 \ . '%+ECONFLIT %.%#,'
1159 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1160 \ . "%+E\u51b2\u7a81 %.%#,"
1162 if a:cmd =~# '^merge' && empty(a:args) &&
1163 \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1164 \ !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1165 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1167 let &l:makeprg = s:sub(g:fugitive_git_executable.' -c core.editor=false '.
1168 \ a:cmd . (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') . ' ' . a:args,
1171 if !empty($GIT_EDITOR)
1172 let old_editor = $GIT_EDITOR
1173 let $GIT_EDITOR = 'false'
1175 execute cd fnameescape(s:repo().tree())
1176 silent noautocmd make!
1177 catch /^Vim\%((\a\+)\)\=:E211/
1178 let err = v:exception
1181 let [&l:mp, &l:efm] = [mp, efm]
1182 if exists('old_editor')
1183 let $GIT_EDITOR = old_editor
1185 execute cd fnameescape(cwd)
1187 call fugitive#reload_status()
1188 if empty(filter(getqflist(),'v:val.valid'))
1189 if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1191 return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1194 let qflist = getqflist()
1199 let e.pattern = '^<<<<<<<'
1202 call fugitive#cwindow()
1204 call setqflist(qflist, 'r')
1209 return exists('err') ? 'echoerr '.string(err) : ''
1212 " Section: Ggrep, Glog
1214 if !exists('g:fugitive_summary_format')
1215 let g:fugitive_summary_format = '%s'
1218 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1219 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1220 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1221 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1223 function! s:Grep(cmd,bang,arg) abort
1224 let grepprg = &grepprg
1225 let grepformat = &grepformat
1226 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1229 execute cd.'`=s:repo().tree()`'
1230 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1231 let &grepformat = '%f:%l:%m'
1232 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1233 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1235 if bufname(entry.bufnr) =~ ':'
1236 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1239 elseif a:arg =~# '\%(^\| \)--cached\>'
1240 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1245 if a:cmd =~# '^l' && exists('changed')
1246 call setloclist(0, list, 'r')
1247 elseif exists('changed')
1248 call setqflist(list, 'r')
1250 if !a:bang && !empty(list)
1251 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1253 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1256 let &grepprg = grepprg
1257 let &grepformat = grepformat
1262 function! s:Log(cmd, line1, line2, ...) abort
1263 let path = s:buffer().path('/')
1264 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1267 let cmd = ['--no-pager', 'log', '--no-color']
1268 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1269 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1270 if s:buffer().commit() =~# '\x\{40\}'
1271 let cmd += [s:buffer().commit()]
1272 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1273 let cmd += [s:buffer().path()[5:-1]]
1276 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1279 let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1281 let cmd += ['--', path[1:-1]]
1284 let grepformat = &grepformat
1285 let grepprg = &grepprg
1286 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1289 execute cd.'`=s:repo().tree()`'
1290 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1291 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1294 let &grepformat = grepformat
1295 let &grepprg = grepprg
1300 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1302 function! s:Edit(cmd,bang,...) abort
1303 let buffer = s:buffer()
1305 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1308 let mywinnr = winnr()
1309 for winnr in range(winnr('$'),1,-1)
1310 if winnr != mywinnr && getwinvar(winnr,'&diff')
1311 execute winnr.'wincmd w'
1323 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1324 let args = join(arglist, ' ')
1326 let git = buffer.repo().git_command()
1327 let last = line('$')
1328 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1330 silent execute '1,'.last.'delete_'
1332 call fugitive#reload_status()
1334 return 'redraw|echo '.string(':!'.git.' '.args)
1336 let temp = resolve(tempname())
1337 let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1338 silent execute a:cmd.' '.temp
1339 if a:cmd =~# 'pedit'
1342 let echo = s:Edit('read',1,args)
1344 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1345 if getline(1) !~# '^diff '
1346 setlocal readonly nomodifiable
1348 if a:cmd =~# 'pedit'
1359 let file = buffer.expand(join(a:000, ' '))
1360 elseif expand('%') ==# ''
1362 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1363 let file = buffer.path(':')
1365 let file = buffer.path('/')
1368 let file = buffer.repo().translate(file)
1370 return 'echoerr v:errmsg'
1373 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1375 return a:cmd.' '.s:fnameescape(file)
1379 function! s:EditComplete(A,L,P) abort
1380 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1383 function! s:EditRunComplete(A,L,P) abort
1385 return s:GitComplete(a:A,a:L,a:P)
1387 return s:repo().superglob(a:A)
1391 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1392 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1393 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1394 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1395 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1396 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1397 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1399 " Section: Gwrite, Gwq
1401 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1402 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1403 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1405 function! s:Write(force,...) abort
1406 if exists('b:fugitive_commit_arguments')
1407 return 'write|bdelete'
1408 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1410 elseif s:buffer().type() == 'index'
1412 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1413 let filename = getline(4)[6:-1]
1416 setlocal buftype=nowrite
1417 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1418 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1420 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1423 let v:errmsg = split(err,"\n")[0]
1424 return 'echoerr v:errmsg'
1428 return 'Gedit '.fnameescape(filename)
1431 let mytab = tabpagenr()
1432 let mybufnr = bufnr('')
1433 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1434 if path =~# '^:\d\>'
1435 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1437 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1438 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) !=# ''
1439 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1440 return 'echoerr v:errmsg'
1442 let file = s:repo().translate(path)
1444 for nr in range(1,bufnr('$'))
1445 if fnamemodify(bufname(nr),':p') ==# file
1450 if treebufnr > 0 && treebufnr != bufnr('')
1451 let temp = tempname()
1452 silent execute '%write '.temp
1453 for tab in [mytab] + range(1,tabpagenr('$'))
1454 for winnr in range(1,tabpagewinnr(tab,'$'))
1455 if tabpagebuflist(tab)[winnr-1] == treebufnr
1456 execute 'tabnext '.tab
1458 execute winnr.'wincmd w'
1459 let restorewinnr = 1
1462 let lnum = line('.')
1463 let last = line('$')
1464 silent execute '$read '.temp
1465 silent execute '1,'.last.'delete_'
1470 if exists('restorewinnr')
1473 execute 'tabnext '.mytab
1479 call writefile(readfile(temp,'b'),file,'b')
1482 execute 'write! '.s:fnameescape(s:repo().translate(path))
1486 let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1488 let error = s:repo().git_chomp_in_tree('add', '--', path)
1491 let v:errmsg = 'fugitive: '.error
1492 return 'echoerr v:errmsg'
1494 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1498 let one = s:repo().translate(':1:'.path)
1499 let two = s:repo().translate(':2:'.path)
1500 let three = s:repo().translate(':3:'.path)
1501 for nr in range(1,bufnr('$'))
1502 let name = fnamemodify(bufname(nr), ':p')
1503 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1504 execute nr.'bdelete'
1509 let zero = s:repo().translate(':0:'.path)
1510 for tab in range(1,tabpagenr('$'))
1511 for winnr in range(1,tabpagewinnr(tab,'$'))
1512 let bufnr = tabpagebuflist(tab)[winnr-1]
1513 let bufname = fnamemodify(bufname(bufnr), ':p')
1514 if bufname ==# zero && bufnr != mybufnr
1515 execute 'tabnext '.tab
1517 execute winnr.'wincmd w'
1518 let restorewinnr = 1
1521 let lnum = line('.')
1522 let last = line('$')
1523 silent execute '$read '.s:fnameescape(file)
1524 silent execute '1,'.last.'delete_'
1529 if exists('restorewinnr')
1532 execute 'tabnext '.mytab
1538 call fugitive#reload_status()
1542 function! s:Wq(force,...) abort
1543 let bang = a:force ? '!' : ''
1544 if exists('b:fugitive_commit_arguments')
1547 let result = call(s:function('s:Write'),[a:force]+a:000)
1548 if result =~# '^\%(write\|wq\|echoerr\)'
1549 return s:sub(result,'^write','wq')
1551 return result.'|quit'.bang
1555 augroup fugitive_commit
1557 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1560 " Section: Gpush, Gfetch
1562 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
1563 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1565 function! s:Dispatch(bang, args)
1566 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1568 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1570 let b:current_compiler = 'git'
1571 let &l:errorformat = s:common_efm
1572 let &l:makeprg = g:fugitive_git_executable . ' ' . a:args
1573 execute cd fnameescape(s:repo().tree())
1574 if exists(':Make') == 2
1577 silent noautocmd make!
1579 return 'call fugitive#cwindow()'
1583 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1584 if empty(cc) | unlet! b:current_compiler | endif
1585 execute cd fnameescape(cwd)
1591 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<f-args>)")
1592 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1593 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<f-args>)")
1595 augroup fugitive_diff
1597 autocmd BufWinLeave *
1598 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1599 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1601 autocmd BufWinEnter *
1602 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1603 \ call s:diffoff() |
1607 function! s:can_diffoff(buf) abort
1608 return getwinvar(bufwinnr(a:buf), '&diff') &&
1609 \ !empty(getbufvar(a:buf, 'git_dir')) &&
1610 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1613 function! fugitive#can_diffoff(buf) abort
1614 return s:can_diffoff(a:buf)
1617 function! s:diff_modifier(count) abort
1618 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1619 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1621 elseif &diffopt =~# 'vertical'
1622 return 'keepalt vert '
1623 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1626 return 'keepalt vert '
1630 function! s:diff_window_count() abort
1632 for nr in range(1,winnr('$'))
1633 let c += getwinvar(nr,'&diff')
1638 function! s:diff_restore() abort
1639 let restore = 'setlocal nodiff noscrollbind'
1640 \ . ' scrollopt=' . &l:scrollopt
1641 \ . (&l:wrap ? ' wrap' : ' nowrap')
1642 \ . ' foldlevel=999'
1643 \ . ' foldmethod=' . &l:foldmethod
1644 \ . ' foldcolumn=' . &l:foldcolumn
1645 \ . ' foldlevel=' . &l:foldlevel
1646 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1647 if has('cursorbind')
1648 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1653 function! s:diffthis() abort
1655 let w:fugitive_diff_restore = s:diff_restore()
1660 function! s:diffoff() abort
1661 if exists('w:fugitive_diff_restore')
1662 execute w:fugitive_diff_restore
1663 unlet w:fugitive_diff_restore
1669 function! s:diffoff_all(dir) abort
1670 for nr in range(1,winnr('$'))
1671 if getwinvar(nr,'&diff')
1673 execute nr.'wincmd w'
1674 let restorewinnr = 1
1676 if exists('b:git_dir') && b:git_dir ==# a:dir
1683 function! s:buffer_compare_age(commit) dict abort
1684 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1685 let my_score = get(scores,':'.self.commit(),0)
1686 let their_score = get(scores,':'.a:commit,0)
1687 if my_score || their_score
1688 return my_score < their_score ? -1 : my_score != their_score
1689 elseif self.commit() ==# a:commit
1692 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1693 if base ==# self.commit()
1695 elseif base ==# a:commit
1698 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1699 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1700 return my_time < their_time ? -1 : my_time != their_time
1703 call s:add_methods('buffer',['compare_age'])
1705 function! s:Diff(vert,...) abort
1706 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1707 if exists(':DiffGitCached')
1708 return 'DiffGitCached'
1709 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1710 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1712 execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1713 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1716 execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1717 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1723 let arg = join(a:000, ' ')
1727 let file = s:buffer().path('/')
1729 let file = s:buffer().path(':0:')
1730 elseif arg =~# '^:/.'
1732 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1734 return 'echoerr v:errmsg'
1737 let file = s:buffer().expand(arg)
1739 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1740 let file = file.s:buffer().path(':')
1743 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1746 let spec = s:repo().translate(file)
1747 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1748 let restore = s:diff_restore()
1749 if exists('+cursorbind')
1752 let w:fugitive_diff_restore = restore
1753 if s:buffer().compare_age(commit) < 0
1754 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1756 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1758 let w:fugitive_diff_restore = restore
1760 if getwinvar('#', '&diff')
1762 call feedkeys("\<C-W>p", 'n')
1766 return 'echoerr v:errmsg'
1770 " Section: Gmove, Gremove
1772 function! s:Move(force,destination) abort
1773 if a:destination =~# '^/'
1774 let destination = a:destination[1:-1]
1776 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1777 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1778 let destination = destination[strlen(s:repo().tree('')):-1]
1781 if isdirectory(s:buffer().spec())
1782 " Work around Vim parser idiosyncrasy
1783 let discarded = s:buffer().setvar('&swapfile',0)
1785 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1787 let v:errmsg = 'fugitive: '.message
1788 return 'echoerr v:errmsg'
1790 let destination = s:repo().tree(destination)
1791 if isdirectory(destination)
1792 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1794 call fugitive#reload_status()
1795 if s:buffer().commit() == ''
1796 if isdirectory(destination)
1797 return 'keepalt edit '.s:fnameescape(destination)
1799 return 'keepalt saveas! '.s:fnameescape(destination)
1802 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1806 function! s:MoveComplete(A,L,P) abort
1808 return s:repo().superglob(a:A)
1810 let matches = split(glob(a:A.'*'),"\n")
1811 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1816 function! s:Remove(force) abort
1817 if s:buffer().commit() ==# ''
1819 elseif s:buffer().commit() ==# '0'
1820 let cmd = ['rm','--cached']
1822 let v:errmsg = 'fugitive: rm not supported here'
1823 return 'echoerr v:errmsg'
1826 let cmd += ['--force']
1828 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1830 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1831 return 'echoerr '.string(v:errmsg)
1833 call fugitive#reload_status()
1834 return 'bdelete'.(a:force ? '!' : '')
1838 augroup fugitive_remove
1840 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1841 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1842 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1848 augroup fugitive_blame
1850 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1851 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1852 autocmd Syntax fugitiveblame call s:BlameSyntax()
1853 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
1854 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
1857 function! s:linechars(pattern) abort
1858 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1859 if exists('*synconcealed') && &conceallevel > 1
1860 for col in range(1, chars)
1861 let chars -= synconcealed(line('.'), col)[0]
1867 function! s:Blame(bang,line1,line2,count,args) abort
1868 if exists('b:fugitive_blamed_bufnr')
1872 if s:buffer().path() == ''
1873 call s:throw('file or blob required')
1875 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1876 call s:throw('unsupported option')
1878 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1879 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1880 if s:buffer().commit() =~# '\D\|..'
1881 let cmd += [s:buffer().commit()]
1883 let cmd += ['--contents', '-']
1885 let cmd += ['--', s:buffer().path()]
1886 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1888 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1891 execute cd.'`=s:repo().tree()`'
1894 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1896 let error = resolve(tempname())
1897 let temp = error.'.fugitiveblame'
1899 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1901 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1908 call s:throw(join(readfile(error),"\n"))
1910 for winnr in range(winnr('$'),1,-1)
1911 call setwinvar(winnr, '&scrollbind', 0)
1912 if exists('+cursorbind')
1913 call setwinvar(winnr, '&cursorbind', 0)
1915 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1916 execute winbufnr(winnr).'bdelete'
1919 let bufnr = bufnr('')
1920 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1921 if exists('+cursorbind')
1922 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
1925 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1928 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1930 setlocal scrollbind nowrap nofoldenable
1931 if exists('+cursorbind')
1934 let top = line('w0') + &scrolloff
1935 let current = line('.')
1936 let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1937 exe 'keepalt leftabove vsplit '.temp
1938 let b:fugitive_blamed_bufnr = bufnr
1939 let w:fugitive_leave = restore
1940 let b:fugitive_blame_arguments = join(a:args,' ')
1944 if exists('+cursorbind')
1947 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1948 if exists('+concealcursor')
1949 setlocal concealcursor=nc conceallevel=2
1951 if exists('+relativenumber')
1952 setlocal norelativenumber
1954 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1955 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1956 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
1957 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1958 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>
1959 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1960 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
1961 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1962 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1963 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1964 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1965 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1966 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1967 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1968 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1979 return 'echoerr v:errmsg'
1983 function! s:BlameCommit(cmd) abort
1984 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1985 if cmd =~# '^echoerr'
1988 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1989 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1991 let path = s:buffer(b:fugitive_blamed_bufnr).path()
1994 if search('^diff .* b/\M'.escape(path,'\').'$','W')
1996 let head = line('.')
1997 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1998 let top = +matchstr(getline('.'),' +\zs\d\+')
1999 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2000 if lnum >= top && lnum <= top + len
2001 let offset = lnum - top
2009 while offset > 0 && line('.') < line('$')
2011 if getline('.') =~# '^[ +]'
2024 function! s:BlameJump(suffix) abort
2025 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2026 if commit =~# '^0\+$'
2029 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2030 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2032 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2034 let args = b:fugitive_blame_arguments
2035 let offset = line('.') - line('w0')
2036 let bufnr = bufnr('%')
2037 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2039 exe winnr.'wincmd w'
2041 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2046 execute 'Gblame '.args
2048 let delta = line('.') - line('w0') - offset
2050 execute 'normal! '.delta."\<C-E>"
2052 execute 'normal! '.(-delta)."\<C-Y>"
2058 let s:hash_colors = {}
2060 function! s:BlameSyntax() abort
2061 let b:current_syntax = 'fugitiveblame'
2062 let conceal = has('conceal') ? ' conceal' : ''
2063 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2064 syn match FugitiveblameBoundary "^\^"
2065 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2066 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2067 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2068 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2069 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2070 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2071 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2072 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2073 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2074 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2075 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2076 hi def link FugitiveblameBoundary Keyword
2077 hi def link FugitiveblameHash Identifier
2078 hi def link FugitiveblameUncommitted Ignore
2079 hi def link FugitiveblameTime PreProc
2080 hi def link FugitiveblameLineNumber Number
2081 hi def link FugitiveblameOriginalFile String
2082 hi def link FugitiveblameOriginalLineNumber Float
2083 hi def link FugitiveblameShort FugitiveblameDelimiter
2084 hi def link FugitiveblameDelimiter Delimiter
2085 hi def link FugitiveblameNotCommittedYet Comment
2087 for lnum in range(1, line('$'))
2088 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2089 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2093 if &t_Co > 16 && exists('g:CSApprox_loaded')
2094 \ && empty(get(s:hash_colors, hash))
2095 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2096 let color = csapprox#per_component#Approximate(r, g, b)
2097 if color == 16 && &background ==# 'dark'
2100 let s:hash_colors[hash] = ' ctermfg='.color
2102 let s:hash_colors[hash] = ''
2104 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2106 call s:RehighlightBlame()
2109 function! s:RehighlightBlame() abort
2110 for [hash, cterm] in items(s:hash_colors)
2111 if !empty(cterm) || has('gui_running')
2112 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2114 exe 'hi link FugitiveblameHash'.hash.' Identifier'
2121 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2123 function! s:Browse(bang,line1,count,...) abort
2125 let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
2127 let expanded = s:buffer().rev()
2129 let expanded = s:buffer().path('/')
2131 let expanded = s:buffer().expand(rev)
2133 let full = s:repo().translate(expanded)
2135 if full =~# '^fugitive://'
2136 let commit = matchstr(full,'://.*//\zs\w\+')
2137 let path = matchstr(full,'://.*//\w\+\zs/.*')
2139 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2143 let path = path[1:-1]
2144 elseif s:repo().bare()
2145 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2148 let path = full[strlen(s:repo().tree())+1:-1]
2149 if path =~# '^\.git/'
2151 elseif isdirectory(full)
2157 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2158 let body = readfile(s:repo().dir(path[5:-1]))[0]
2159 if body =~# '^\x\{40\}$'
2163 elseif body =~# '^ref: refs/'
2164 let path = '.git/' . matchstr(body,'ref: \zs.*')
2168 if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
2169 let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
2170 elseif path =~# '^\.git/refs/remotes/.'
2171 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
2173 let remote = 'origin'
2174 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
2175 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
2176 let branch = s:sub(path,'^\.git/refs/\w+/','')
2178 if filereadable(s:repo().dir('refs/remotes/'.branch))
2179 let remote = matchstr(branch,'[^/]\+')
2180 let rev = rev[strlen(remote)+1:-1]
2183 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
2186 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
2187 if remote =~# '^\.\=$'
2188 let remote = 'origin'
2189 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
2190 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
2196 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
2201 for Handler in g:fugitive_browse_handlers
2202 let url = call(Handler, [{
2209 \ 'line1': a:count > 0 ? a:line1 : 0,
2210 \ 'line2': a:count > 0 ? a:count : 0}])
2217 call s:throw("Instaweb failed to start and '".remote."' is not a supported remote")
2224 return 'echomsg '.string(url)
2225 elseif exists(':Browse') == 2
2226 return 'echomsg '.string(url).'|Browse '.url
2228 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2231 return 'echoerr v:errmsg'
2235 function! s:github_url(opts, ...) abort
2236 if a:0 || type(a:opts) != type({})
2239 let domain_pattern = 'github\.com'
2240 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2241 for domain in domains
2242 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2244 let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2248 let path = a:opts.path
2249 if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
2250 let root = 'http://' . s:sub(repo,':','/')
2252 let root = 'https://' . s:sub(repo,':','/')
2254 if path =~# '^\.git/refs/heads/'
2255 let branch = a:opts.repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
2257 return root . '/commits/' . path[16:-1]
2259 return root . '/commits/' . branch
2261 elseif path =~# '^\.git/refs/.'
2262 return root . '/commits/' . matchstr(path,'[^/]\+$')
2263 elseif path =~# '.git/\%(config$\|hooks\>\)'
2264 return root . '/admin'
2265 elseif path =~# '^\.git\>'
2268 if a:opts.revision =~# '^[[:alnum:]._-]\+:'
2269 let commit = matchstr(a:opts.revision,'^[^:]*')
2270 elseif a:opts.commit =~# '^\d\=$'
2271 let local = matchstr(a:opts.repo.head_ref(),'\<refs/heads/\zs.*')
2272 let commit = a:opts.repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2277 let commit = a:opts.commit
2279 if a:opts.type == 'tree'
2280 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2281 elseif a:opts.type == 'blob'
2282 let url = root . '/blob/' . commit . '/' . path
2283 if get(a:opts, 'line2') && a:opts.line1 == a:opts.line2
2284 let url .= '#L' . a:opts.line1
2285 elseif get(a:opts, 'line2')
2286 let url .= '#L' . a:opts.line1 . '-' . a:opts.line2
2288 elseif a:opts.type == 'tag'
2289 let commit = matchstr(getline(3),'^tag \zs.*')
2290 let url = root . '/tree/' . commit
2292 let url = root . '/commit/' . commit
2297 function! s:instaweb_url(opts) abort
2298 let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2299 if output =~# 'http://'
2300 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2304 if a:opts.path =~# '^\.git/refs/.'
2305 return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2306 elseif a:opts.path =~# '^\.git\>'
2310 if a:opts.commit =~# '^\x\{40\}$'
2311 if a:opts.type ==# 'commit'
2312 let url .= ';a=commit'
2314 let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2316 if a:opts.type ==# 'blob'
2317 let tmp = tempname()
2318 silent execute 'write !'.a:opts.repo.git_command('hash-object','-w','--stdin').' > '.tmp
2319 let url .= ';h=' . readfile(tmp)[0]
2322 let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2324 call s:throw('fugitive: cannot browse uncommitted file')
2327 let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2329 if a:opts.path !=# ''
2330 let url .= ';f=' . a:opts.path
2332 if get(a:opts, 'line1')
2333 let url .= '#l' . a:opts.line1
2338 if !exists('g:fugitive_browse_handlers')
2339 let g:fugitive_browse_handlers = []
2342 call extend(g:fugitive_browse_handlers,
2343 \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2345 " Section: File access
2347 function! s:ReplaceCmd(cmd,...) abort
2348 let fn = expand('%:p')
2349 let tmp = tempname()
2354 let old_index = $GIT_INDEX_FILE
2355 let $GIT_INDEX_FILE = a:1
2357 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2361 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2362 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2364 call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2367 if exists('old_index')
2368 let $GIT_INDEX_FILE = old_index
2371 silent exe 'keepalt file '.tmp
2376 silent exe 'keepalt file '.s:fnameescape(fn)
2377 catch /^Vim\%((\a\+)\)\=:E302/
2380 if fnamemodify(bufname('$'), ':p') ==# tmp
2381 silent execute 'bwipeout '.bufnr('$')
2383 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2387 function! s:BufReadIndex() abort
2388 if !exists('b:fugitive_display_format')
2389 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2391 let b:fugitive_display_format = b:fugitive_display_format % 2
2392 let b:fugitive_type = 'index'
2394 let b:git_dir = s:repo().dir()
2395 setlocal noro ma nomodeline
2396 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2399 let index = expand('%:p')
2401 if b:fugitive_display_format
2402 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2405 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2407 if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2408 let cmd = s:repo().git_command('status')
2410 let cmd = s:repo().git_command(
2411 \ '-c', 'status.displayCommentPrefix=true',
2412 \ '-c', 'color.status=false',
2413 \ '-c', 'status.short=false',
2417 execute cd.'`=s:repo().tree()`'
2418 call s:ReplaceCmd(cmd, index)
2423 set foldtext=fugitive#foldtext()
2425 setlocal ro noma nomod noswapfile
2426 if &bufhidden ==# ''
2427 setlocal bufhidden=delete
2432 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2433 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2434 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2435 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2436 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2437 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2438 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2439 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2440 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2441 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2442 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2443 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2444 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2445 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2446 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2447 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2448 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2449 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2450 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2451 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2452 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2453 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2454 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2455 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2456 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2457 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2459 return 'echoerr v:errmsg'
2463 function! s:FileRead() abort
2465 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2466 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2467 let hash = repo.rev_parse(path)
2471 let type = repo.git_chomp('cat-file','-t',hash)
2473 " TODO: use count, if possible
2474 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2476 return 'echoerr v:errmsg'
2480 function! s:BufReadIndexFile() abort
2482 let b:fugitive_type = 'blob'
2483 let b:git_dir = s:repo().dir()
2485 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2487 if &bufhidden ==# ''
2488 setlocal bufhidden=delete
2493 catch /^fugitive: rev-parse/
2494 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2497 return 'echoerr v:errmsg'
2501 function! s:BufWriteIndexFile() abort
2502 let tmp = tempname()
2504 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2505 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2506 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2507 let sha1 = readfile(tmp)[0]
2508 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2510 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2512 let info = old_mode.' '.sha1.' '.stage."\t".path
2513 call writefile([info],tmp)
2515 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2517 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2519 if v:shell_error == 0
2521 if exists('#BufWritePost')
2522 execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2524 call fugitive#reload_status()
2527 return 'echoerr '.string('fugitive: '.error)
2534 function! s:BufReadObject() abort
2537 let b:git_dir = s:repo().dir()
2538 let hash = s:buffer().sha1()
2539 if !exists("b:fugitive_type")
2540 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2542 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2543 return "echoerr 'fugitive: unrecognized git type'"
2545 let firstline = getline('.')
2546 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2547 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2550 if b:fugitive_type !=# 'blob'
2554 let pos = getpos('.')
2555 silent keepjumps %delete_
2559 if b:fugitive_type ==# 'tree'
2560 let b:fugitive_display_format = b:fugitive_display_format % 2
2561 if b:fugitive_display_format
2562 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2564 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2566 elseif b:fugitive_type ==# 'tag'
2567 let b:fugitive_display_format = b:fugitive_display_format % 2
2568 if b:fugitive_display_format
2569 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2571 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2573 elseif b:fugitive_type ==# 'commit'
2574 let b:fugitive_display_format = b:fugitive_display_format % 2
2575 if b:fugitive_display_format
2576 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2578 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))
2579 keepjumps call search('^parent ')
2580 if getline('.') ==# 'parent '
2581 silent keepjumps delete_
2583 silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2585 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2587 silent keepjumps delete_
2591 elseif b:fugitive_type ==# 'blob'
2592 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2596 keepjumps call setpos('.',pos)
2597 setlocal ro noma nomod noswapfile
2598 if &bufhidden ==# ''
2599 setlocal bufhidden=delete
2601 if b:fugitive_type !=# 'blob'
2602 setlocal filetype=git foldmethod=syntax
2603 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2604 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2612 return 'echoerr v:errmsg'
2616 augroup fugitive_files
2618 autocmd BufReadCmd index{,.lock}
2619 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2620 \ exe s:BufReadIndex() |
2621 \ elseif filereadable(expand('<amatch>')) |
2625 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2626 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2627 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2628 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2629 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2630 autocmd FileType git
2631 \ if exists('b:git_dir') |
2632 \ call s:JumpInit() |
2636 " Section: Temp files
2638 if !exists('s:temp_files')
2639 let s:temp_files = {}
2642 augroup fugitive_temp
2644 autocmd BufNewFile,BufReadPost *
2645 \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2646 \ let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2647 \ let b:git_type = 'temp' |
2648 \ let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2649 \ call fugitive#detect(expand('<afile>:p')) |
2650 \ setlocal bufhidden=delete nobuflisted |
2651 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2655 " Section: Go to file
2657 function! s:JumpInit() abort
2658 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2660 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2661 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2662 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2663 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>
2664 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2665 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2666 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2667 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2668 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2669 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2670 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2671 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2672 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2676 function! s:GF(mode) abort
2678 let buffer = s:buffer()
2679 let myhash = buffer.sha1()
2680 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2681 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2684 if buffer.type('tree')
2685 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2686 if showtree && line('.') == 1
2688 elseif showtree && line('.') > 2
2689 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2690 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2691 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2694 elseif buffer.type('blob')
2695 let ref = expand("<cfile>")
2697 let sha1 = buffer.repo().rev_parse(ref)
2701 return s:Edit(a:mode,0,ref)
2707 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2708 let ref = matchstr(getline('.'),'\x\{40\}')
2709 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2710 return s:Edit(a:mode,0,file)
2712 elseif getline('.') =~# '^#\trenamed:.* -> '
2713 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2714 return s:Edit(a:mode,0,file)
2715 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2716 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2717 return s:Edit(a:mode,0,file)
2718 elseif getline('.') =~# '^#\t.'
2719 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2720 return s:Edit(a:mode,0,file)
2721 elseif getline('.') =~# ': needs merge$'
2722 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2723 return s:Edit(a:mode,0,file).'|Gdiff'
2725 elseif getline('.') ==# '# Not currently on any branch.'
2726 return s:Edit(a:mode,0,'HEAD')
2727 elseif getline('.') =~# '^# On branch '
2728 let file = 'refs/heads/'.getline('.')[12:]
2729 return s:Edit(a:mode,0,file)
2730 elseif getline('.') =~# "^# Your branch .*'"
2731 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2732 return s:Edit(a:mode,0,file)
2735 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2737 if getline('.') =~# '^ref: '
2738 let ref = strpart(getline('.'),5)
2740 elseif getline('.') =~# '^commit \x\{40\}\>'
2741 let ref = matchstr(getline('.'),'\x\{40\}')
2742 return s:Edit(a:mode,0,ref)
2744 elseif getline('.') =~# '^parent \x\{40\}\>'
2745 let ref = matchstr(getline('.'),'\x\{40\}')
2746 let line = line('.')
2748 while getline(line) =~# '^parent '
2752 return s:Edit(a:mode,0,ref)
2754 elseif getline('.') =~ '^tree \x\{40\}$'
2755 let ref = matchstr(getline('.'),'\x\{40\}')
2756 if s:repo().rev_parse(myhash.':') == ref
2757 let ref = myhash.':'
2759 return s:Edit(a:mode,0,ref)
2761 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2762 let ref = matchstr(getline('.'),'\x\{40\}')
2763 let type = matchstr(getline(line('.')+1),'type \zs.*')
2765 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2768 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2769 let ref = matchstr(getline('.'),'\x\{40\}')
2770 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2772 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2773 let ref = getline('.')[4:]
2775 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2776 let type = getline('.')[0]
2777 let lnum = line('.') - 1
2779 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2780 if getline(lnum) =~# '^[ '.type.']'
2785 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2786 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2787 let dcmd = '+'.offset.'|normal! zv'
2790 elseif getline('.') =~# '^rename from '
2791 let ref = 'a/'.getline('.')[12:]
2792 elseif getline('.') =~# '^rename to '
2793 let ref = 'b/'.getline('.')[10:]
2795 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2796 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2797 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2800 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2801 let line = getline(line('.')-1)
2802 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2803 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2806 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2807 let ref = getline('.')
2809 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
2810 return s:Edit(a:mode,0,expand('<cword>'))
2817 let ref = s:sub(ref,'^a/','HEAD:')
2818 let ref = s:sub(ref,'^b/',':0:')
2820 let dref = s:sub(dref,'^a/','HEAD:')
2823 let ref = s:sub(ref,'^a/',myhash.'^:')
2824 let ref = s:sub(ref,'^b/',myhash.':')
2826 let dref = s:sub(dref,'^a/',myhash.'^:')
2830 if ref ==# '/dev/null'
2832 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2836 return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2838 return s:Edit(a:mode,0,ref)
2844 return 'echoerr v:errmsg'
2848 " Section: Statusline
2850 function! s:repo_head_ref() dict abort
2851 if !filereadable(self.dir('HEAD'))
2854 return readfile(self.dir('HEAD'))[0]
2857 call s:add_methods('repo',['head_ref'])
2859 function! fugitive#statusline(...) abort
2860 if !exists('b:git_dir')
2864 if s:buffer().commit() != ''
2865 let status .= ':' . s:buffer().commit()[0:7]
2867 let status .= '('.fugitive#head(7).')'
2868 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2869 return ',GIT'.status
2871 return '[Git'.status.']'
2875 function! fugitive#head(...) abort
2876 if !exists('b:git_dir')
2880 return s:repo().head(a:0 ? a:1 : 0)
2885 function! fugitive#foldtext() abort
2886 if &foldmethod !=# 'syntax'
2888 elseif getline(v:foldstart) =~# '^diff '
2889 let [add, remove] = [-1, -1]
2891 for lnum in range(v:foldstart, v:foldend)
2892 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2893 let filename = getline(lnum)[6:-1]
2895 if getline(lnum) =~# '^+'
2897 elseif getline(lnum) =~# '^-'
2899 elseif getline(lnum) =~# '^Binary '
2904 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2907 let filename = getline(v:foldstart)[5:-1]
2910 return 'Binary: '.filename
2912 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2914 elseif getline(v:foldstart) =~# '^# .*:$'
2915 let lines = getline(v:foldstart, v:foldend)
2916 call filter(lines, 'v:val =~# "^#\t"')
2917 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2918 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2919 return getline(v:foldstart).' '.join(lines, ', ')
2924 augroup fugitive_foldtext
2926 autocmd User Fugitive
2927 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2928 \ set foldtext=fugitive#foldtext() |