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 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('<amatch>: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"))
407 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
411 let base = s:sub(a:base,'^/','')
412 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
413 call map(matches,'s:shellslash(v:val)')
414 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
415 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
416 let results += matches
420 elseif a:base =~# '^:'
421 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
422 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
423 if a:base !~# '^:[0-3]\%(:\|$\)'
424 call filter(entries,'v:val[1] == "0"')
425 call map(entries,'v:val[2:-1]')
427 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
431 let tree = matchstr(a:base,'.*[:/]')
432 let entries = split(self.git_chomp('ls-tree',tree),"\n")
433 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
434 call map(entries,'tree.s:sub(v:val,".*\t","")')
435 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
439 call s:add_methods('repo',['dirglob','superglob'])
441 function! s:repo_config(conf) dict abort
442 return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
445 function! s:repo_user() dict abort
446 let username = s:repo().config('user.name')
447 let useremail = s:repo().config('user.email')
448 return username.' <'.useremail.'>'
451 function! s:repo_aliases() dict abort
452 if !has_key(self,'_aliases')
453 let self._aliases = {}
454 for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
455 let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
461 call s:add_methods('repo',['config', 'user', 'aliases'])
463 function! s:repo_keywordprg() dict abort
464 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
465 if has('gui_running') && !has('win32')
466 return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
468 return g:fugitive_git_executable . args . ' show'
472 call s:add_methods('repo',['keywordprg'])
476 let s:buffer_prototype = {}
478 function! s:buffer(...) abort
479 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
480 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
481 if buffer.getvar('git_dir') !=# ''
484 call s:throw('not a git repository: '.expand('%:p'))
487 function! fugitive#buffer(...) abort
488 return s:buffer(a:0 ? a:1 : '%')
491 function! s:buffer_getvar(var) dict abort
492 return getbufvar(self['#'],a:var)
495 function! s:buffer_setvar(var,value) dict abort
496 return setbufvar(self['#'],a:var,a:value)
499 function! s:buffer_getline(lnum) dict abort
500 return get(getbufline(self['#'], a:lnum), 0, '')
503 function! s:buffer_repo() dict abort
504 return s:repo(self.getvar('git_dir'))
507 function! s:buffer_type(...) dict abort
508 if self.getvar('fugitive_type') != ''
509 let type = self.getvar('fugitive_type')
510 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
512 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
514 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
516 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
518 elseif isdirectory(self.spec())
519 let type = 'directory'
520 elseif self.spec() == ''
526 return !empty(filter(copy(a:000),'v:val ==# type'))
534 function! s:buffer_spec() dict abort
535 let bufname = bufname(self['#'])
537 for i in split(bufname,'[^:]\zs\\')
538 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
540 return s:shellslash(fnamemodify(retval,':p'))
545 function! s:buffer_spec() dict abort
546 let bufname = bufname(self['#'])
547 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
552 function! s:buffer_name() dict abort
556 function! s:buffer_commit() dict abort
557 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
560 function! s:buffer_path(...) dict abort
561 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
563 let rev = s:sub(rev,'\w*','')
564 elseif self.spec()[0 : len(self.repo().dir())] ==# self.repo().dir() . '/'
565 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
566 elseif !self.repo().bare() && self.spec()[0 : len(self.repo().tree())] ==# self.repo().tree() . '/'
567 let rev = self.spec()[strlen(self.repo().tree()) : -1]
569 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
572 function! s:buffer_rev() dict abort
573 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
575 return ':'.rev[0].':'.rev[2:-1]
577 return s:sub(rev,'/',':')
578 elseif self.spec() =~ '\.git/index$'
580 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
581 return self.spec()[strlen(self.repo().dir())+1 : -1]
583 return self.path('/')
587 function! s:buffer_sha1() dict abort
588 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
589 return self.repo().rev_parse(self.rev())
595 function! s:buffer_expand(rev) dict abort
596 if a:rev =~# '^:[0-3]$'
597 let file = a:rev.self.path(':')
598 elseif a:rev =~# '^[-:]/$'
599 let file = '/'.self.path()
600 elseif a:rev =~# '^-'
601 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
602 elseif a:rev =~# '^@{'
603 let file = 'HEAD'.a:rev.self.path(':')
604 elseif a:rev =~# '^[~^]'
605 let commit = s:sub(self.commit(),'^\d=$','HEAD')
606 let file = commit.a:rev.self.path(':')
610 return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
613 function! s:buffer_containing_commit() dict abort
614 if self.commit() =~# '^\d$'
616 elseif self.commit() =~# '.'
623 function! s:buffer_up(...) dict abort
625 let c = a:0 ? a:1 : 1
631 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
633 elseif rev =~# '^/\|:.*/'
634 let rev = s:sub(rev, '.*\zs/.*', '')
636 let rev = matchstr(rev, '^[^:]*:')
647 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
651 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
653 function! s:ExecuteInTree(cmd) abort
654 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
657 execute cd.'`=s:repo().tree()`'
664 function! s:Git(bang, args) abort
666 return s:Edit('edit', 1, a:args)
668 let git = g:fugitive_git_executable
669 if has('gui_running') && !has('win32')
670 let git .= ' --no-pager'
672 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
673 if exists(':terminal')
674 let dir = s:repo().tree()
676 execute 'lcd' fnameescape(dir)
677 execute 'terminal' git args
679 call s:ExecuteInTree('!'.git.' '.args)
681 call fugitive#reload_status()
684 return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
687 function! fugitive#git_commands() abort
688 if !exists('s:exec_path')
689 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
691 return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
694 function! s:GitComplete(A, L, P) abort
695 if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
696 let cmds = fugitive#git_commands()
697 return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
699 return s:repo().superglob(a:A)
705 function! s:DirComplete(A,L,P) abort
706 let matches = s:repo().dirglob(a:A)
710 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>)`")
711 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>)`")
715 call s:command("-bar Gstatus :execute s:Status()")
716 augroup fugitive_status
719 autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
720 autocmd BufDelete term://* call fugitive#reload_status()
724 function! s:Status() abort
728 setlocal foldmethod=syntax foldlevel=1
729 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
731 return 'echoerr v:errmsg'
736 function! fugitive#reload_status() abort
737 if exists('s:reloading_status')
741 let s:reloading_status = 1
742 let mytab = tabpagenr()
743 for tab in [mytab] + range(1,tabpagenr('$'))
744 for winnr in range(1,tabpagewinnr(tab,'$'))
745 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
746 execute 'tabnext '.tab
748 execute winnr.'wincmd w'
753 call s:BufReadIndex()
756 if exists('restorewinnr')
759 execute 'tabnext '.mytab
765 unlet! s:reloading_status
769 function! s:stage_info(lnum) abort
770 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
772 if has('multi_byte_encoding')
773 let colon = '\%(:\|\%uff1a\)'
777 while lnum && getline(lnum) !~# colon.'$'
782 elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
783 return [matchstr(filename, colon.' *\zs.*'), 'staged']
784 elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
785 return [filename, 'untracked']
786 elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
787 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
788 elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
789 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
791 return ['', 'unknown']
795 function! s:StageNext(count) abort
796 for i in range(a:count)
797 call search('^#\t.*','W')
802 function! s:StagePrevious(count) abort
803 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
804 return 'CtrlP '.fnameescape(s:repo().tree())
806 for i in range(a:count)
807 call search('^#\t.*','Wbe')
813 function! s:StageReloadSeek(target,lnum1,lnum2) abort
815 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
816 if f !=# '' | let jump = f | endif
817 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
818 if f !=# '' | let jump = f | endif
822 call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
825 function! s:StageUndo() abort
826 let [filename, section] = s:stage_info(line('.'))
831 let hash = repo.git_chomp('hash-object', '-w', filename)
833 if section ==# 'untracked'
834 call delete(s:repo().tree(filename))
835 elseif section ==# 'unstaged'
836 call repo.git_chomp_in_tree('checkout', '--', filename)
838 call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
840 call s:StageReloadSeek(filename, line('.'), line('.'))
842 return 'checktime|redraw|echomsg ' .
843 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
847 function! s:StageDiff(diff) abort
848 let [filename, section] = s:stage_info(line('.'))
849 if filename ==# '' && section ==# 'staged'
850 return 'Git! diff --no-ext-diff --cached'
851 elseif filename ==# ''
852 return 'Git! diff --no-ext-diff'
853 elseif filename =~# ' -> '
854 let [old, new] = split(filename,' -> ')
855 execute 'Gedit '.s:fnameescape(':0:'.new)
856 return a:diff.' HEAD:'.s:fnameescape(old)
857 elseif section ==# 'staged'
858 execute 'Gedit '.s:fnameescape(':0:'.filename)
861 execute 'Gedit '.s:fnameescape('/'.filename)
866 function! s:StageDiffEdit() abort
867 let [filename, section] = s:stage_info(line('.'))
868 let arg = (filename ==# '' ? '.' : filename)
869 if section ==# 'staged'
870 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
871 elseif section ==# 'untracked'
873 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
877 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
878 call search('^# .*:$','W')
881 call s:StageReloadSeek(arg,line('.'),line('.'))
885 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
889 function! s:StageToggle(lnum1,lnum2) abort
890 if a:lnum1 == 1 && a:lnum2 == 1
891 return 'Gedit /.git|call search("^index$", "wc")'
895 for lnum in range(a:lnum1,a:lnum2)
896 let [filename, section] = s:stage_info(lnum)
898 if getline('.') =~# '^# .*:$'
899 if section ==# 'staged'
900 call repo.git_chomp_in_tree('reset','-q')
903 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
904 call search('^# .*:$','W')
907 elseif section ==# 'unstaged'
908 call repo.git_chomp_in_tree('add','-u')
911 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
912 call search('^# .*:$','W')
916 call repo.git_chomp_in_tree('add','.')
919 call search('^# .*:$','W')
927 if filename =~ ' -> '
928 let cmd = ['mv','--'] + reverse(split(filename,' -> '))
929 let filename = cmd[-1]
930 elseif section ==# 'staged'
931 let cmd = ['reset','-q','--',filename]
932 elseif getline(lnum) =~# '^#\tdeleted:'
933 let cmd = ['rm','--',filename]
934 elseif getline(lnum) =~# '^#\tmodified:'
935 let cmd = ['add','--',filename]
937 let cmd = ['add','-A','--',filename]
939 if !exists('first_filename')
940 let first_filename = filename
942 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
944 if exists('first_filename')
945 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
947 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
949 return 'echoerr v:errmsg'
954 function! s:StagePatch(lnum1,lnum2) abort
958 for lnum in range(a:lnum1,a:lnum2)
959 let [filename, section] = s:stage_info(lnum)
960 if getline('.') =~# '^# .*:$' && section ==# 'staged'
961 return 'Git reset --patch'
962 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
963 return 'Git add --patch'
964 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
965 return 'Git add -N .'
966 elseif filename ==# ''
969 if !exists('first_filename')
970 let first_filename = filename
973 if filename =~ ' -> '
974 let reset += [split(filename,' -> ')[1]]
975 elseif section ==# 'staged'
976 let reset += [filename]
977 elseif getline(lnum) !~# '^#\tdeleted:'
978 let add += [filename]
983 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
986 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
988 if exists('first_filename')
992 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
995 return 'echoerr v:errmsg'
1002 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
1004 function! s:Commit(args, ...) abort
1005 let repo = a:0 ? a:1 : s:repo()
1006 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1008 let msgfile = repo.dir('COMMIT_EDITMSG')
1009 let outfile = tempname()
1010 let errorfile = tempname()
1013 execute cd.s:fnameescape(repo.tree())
1016 let old_editor = $GIT_EDITOR
1017 let $GIT_EDITOR = 'false'
1019 let command = 'env GIT_EDITOR=false '
1021 let command .= repo.git_command('commit').' '.a:args
1023 noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1024 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1025 noautocmd execute '!'.command.' 2> '.errorfile
1027 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1032 if !has('gui_running')
1036 if filereadable(outfile)
1037 for line in readfile(outfile)
1043 let errors = readfile(errorfile)
1044 let error = get(errors,-2,get(errors,-1,'!'))
1045 if error =~# 'false''\=\.$'
1047 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1048 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1049 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1050 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1051 let args = '-F '.s:shellesc(msgfile).' '.args
1052 if args !~# '\%(^\| \)--cleanup\>'
1053 let args = '--cleanup=strip '.args
1055 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1056 execute 'keepalt edit '.s:fnameescape(msgfile)
1057 elseif a:args =~# '\%(^\| \)-\%(-verbose\|\w*v\)\>'
1058 execute 'keepalt '.(tabpagenr()-1).'tabedit '.s:fnameescape(msgfile)
1059 elseif s:buffer().type() ==# 'index'
1060 execute 'keepalt edit '.s:fnameescape(msgfile)
1061 execute (search('^#','n')+1).'wincmd+'
1062 setlocal nopreviewwindow
1064 execute 'keepalt split '.s:fnameescape(msgfile)
1066 let b:fugitive_commit_arguments = args
1067 setlocal bufhidden=wipe filetype=gitcommit
1069 elseif error ==# '!'
1076 return 'echoerr v:errmsg'
1078 if exists('old_editor')
1079 let $GIT_EDITOR = old_editor
1081 call delete(outfile)
1082 call delete(errorfile)
1083 call fugitive#reload_status()
1087 function! s:CommitComplete(A,L,P) abort
1088 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1089 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']
1090 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1092 return s:repo().superglob(a:A)
1096 function! s:FinishCommit() abort
1097 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1099 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1100 return s:Commit(args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1105 " Section: Gmerge, Gpull
1107 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1108 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1109 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1110 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1112 function! s:RevisionComplete(A, L, P) abort
1113 return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1114 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1117 function! s:RemoteComplete(A, L, P) abort
1118 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1120 let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1121 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1122 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1124 let matches = split(s:repo().git_chomp('remote'), "\n")
1126 return join(matches, "\n")
1129 function! fugitive#cwindow() abort
1130 if &buftype == 'quickfix'
1134 if &buftype == 'quickfix'
1140 let s:common_efm = ''
1142 \ . '%+Eusage:%.%#,'
1143 \ . '%+Eerror:%.%#,'
1144 \ . '%+Efatal:%.%#,'
1145 \ . '%-G%.%#%\e[K%.%#,'
1146 \ . '%-G%.%#%\r%.%\+'
1148 function! s:Merge(cmd, bang, args) abort
1149 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1151 let [mp, efm] = [&l:mp, &l:efm]
1152 let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1154 let &l:errorformat = ''
1155 \ . '%-Gerror:%.%#false''.,'
1156 \ . '%-G%.%# ''git commit'' %.%#,'
1157 \ . '%+Emerge:%.%#,'
1158 \ . s:common_efm . ','
1159 \ . '%+ECannot %.%#: You have unstaged changes.,'
1160 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1161 \ . '%+EThere is no tracking information for the current branch.,'
1162 \ . '%+EYou are not currently on a branch. Please specify which,'
1163 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1164 \ . 'CONFLICT (%m): Merge conflict in %f,'
1165 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1166 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1167 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1168 \ . '%+ECONFLICT %.%#,'
1169 \ . '%+EKONFLIKT %.%#,'
1170 \ . '%+ECONFLIT %.%#,'
1171 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1172 \ . "%+E\u51b2\u7a81 %.%#,"
1174 if a:cmd =~# '^merge' && empty(a:args) &&
1175 \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1176 \ !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1177 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1179 let &l:makeprg = s:sub(g:fugitive_git_executable . ' ' . a:cmd .
1180 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
1181 \ ' ' . a:args, ' *$', '')
1183 if !empty($GIT_EDITOR) || has('win32')
1184 let old_editor = $GIT_EDITOR
1185 let $GIT_EDITOR = 'false'
1187 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1189 execute cd fnameescape(s:repo().tree())
1190 silent noautocmd make!
1191 catch /^Vim\%((\a\+)\)\=:E211/
1192 let err = v:exception
1195 let [&l:mp, &l:efm] = [mp, efm]
1196 if exists('old_editor')
1197 let $GIT_EDITOR = old_editor
1199 execute cd fnameescape(cwd)
1201 call fugitive#reload_status()
1202 if empty(filter(getqflist(),'v:val.valid'))
1203 if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1205 return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1208 let qflist = getqflist()
1213 let e.pattern = '^<<<<<<<'
1216 call fugitive#cwindow()
1218 call setqflist(qflist, 'r')
1223 return exists('err') ? 'echoerr '.string(err) : ''
1226 " Section: Ggrep, Glog
1228 if !exists('g:fugitive_summary_format')
1229 let g:fugitive_summary_format = '%s'
1232 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1233 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1234 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1235 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1237 function! s:Grep(cmd,bang,arg) abort
1238 let grepprg = &grepprg
1239 let grepformat = &grepformat
1240 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1243 execute cd.'`=s:repo().tree()`'
1244 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
1245 let &grepformat = '%f:%l:%m'
1246 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1247 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1249 if bufname(entry.bufnr) =~ ':'
1250 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1253 elseif a:arg =~# '\%(^\| \)--cached\>'
1254 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1259 if a:cmd =~# '^l' && exists('changed')
1260 call setloclist(0, list, 'r')
1261 elseif exists('changed')
1262 call setqflist(list, 'r')
1264 if !a:bang && !empty(list)
1265 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1267 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1270 let &grepprg = grepprg
1271 let &grepformat = grepformat
1276 function! s:Log(cmd, line1, line2, ...) abort
1277 let path = s:buffer().path('/')
1278 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1281 let cmd = ['--no-pager', 'log', '--no-color']
1282 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1283 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1284 if s:buffer().commit() =~# '\x\{40\}'
1285 let cmd += [s:buffer().commit()]
1286 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1287 let cmd += [s:buffer().path()[5:-1]]
1290 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1293 let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1295 let cmd += ['--', path[1:-1]]
1298 let grepformat = &grepformat
1299 let grepprg = &grepprg
1300 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1303 execute cd.'`=s:repo().tree()`'
1304 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1305 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1308 let &grepformat = grepformat
1309 let &grepprg = grepprg
1314 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1316 function! s:Edit(cmd,bang,...) abort
1317 let buffer = s:buffer()
1319 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1322 let mywinnr = winnr()
1323 for winnr in range(winnr('$'),1,-1)
1324 if winnr != mywinnr && getwinvar(winnr,'&diff')
1325 execute winnr.'wincmd w'
1337 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1338 let args = join(arglist, ' ')
1340 let git = buffer.repo().git_command()
1341 let last = line('$')
1342 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1344 silent execute '1,'.last.'delete_'
1346 call fugitive#reload_status()
1348 return 'redraw|echo '.string(':!'.git.' '.args)
1350 let temp = resolve(tempname())
1351 let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1352 silent execute a:cmd.' '.temp
1353 if a:cmd =~# 'pedit'
1356 let echo = s:Edit('read',1,args)
1358 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1359 if getline(1) !~# '^diff '
1360 setlocal readonly nomodifiable
1362 if a:cmd =~# 'pedit'
1373 let file = buffer.expand(join(a:000, ' '))
1374 elseif expand('%') ==# ''
1376 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1377 let file = buffer.path(':')
1379 let file = buffer.path('/')
1382 let file = buffer.repo().translate(file)
1384 return 'echoerr v:errmsg'
1387 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1389 return a:cmd.' '.s:fnameescape(file)
1393 function! s:EditComplete(A,L,P) abort
1394 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1397 function! s:EditRunComplete(A,L,P) abort
1399 return s:GitComplete(a:A,a:L,a:P)
1401 return s:repo().superglob(a:A)
1405 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1406 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1407 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1408 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1409 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1410 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1411 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1413 " Section: Gwrite, Gwq
1415 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1416 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1417 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1419 function! s:Write(force,...) abort
1420 if exists('b:fugitive_commit_arguments')
1421 return 'write|bdelete'
1422 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1424 elseif s:buffer().type() == 'index'
1426 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1427 let filename = getline(4)[6:-1]
1430 setlocal buftype=nowrite
1431 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1432 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1434 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1437 let v:errmsg = split(err,"\n")[0]
1438 return 'echoerr v:errmsg'
1442 return 'Gedit '.fnameescape(filename)
1445 let mytab = tabpagenr()
1446 let mybufnr = bufnr('')
1447 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1448 if path =~# '^:\d\>'
1449 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1451 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1452 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) !=# ''
1453 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1454 return 'echoerr v:errmsg'
1456 let file = s:repo().translate(path)
1458 for nr in range(1,bufnr('$'))
1459 if fnamemodify(bufname(nr),':p') ==# file
1464 if treebufnr > 0 && treebufnr != bufnr('')
1465 let temp = tempname()
1466 silent execute '%write '.temp
1467 for tab in [mytab] + range(1,tabpagenr('$'))
1468 for winnr in range(1,tabpagewinnr(tab,'$'))
1469 if tabpagebuflist(tab)[winnr-1] == treebufnr
1470 execute 'tabnext '.tab
1472 execute winnr.'wincmd w'
1473 let restorewinnr = 1
1476 let lnum = line('.')
1477 let last = line('$')
1478 silent execute '$read '.temp
1479 silent execute '1,'.last.'delete_'
1484 if exists('restorewinnr')
1487 execute 'tabnext '.mytab
1493 call writefile(readfile(temp,'b'),file,'b')
1496 execute 'write! '.s:fnameescape(s:repo().translate(path))
1500 let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1502 let error = s:repo().git_chomp_in_tree('add', '--', path)
1505 let v:errmsg = 'fugitive: '.error
1506 return 'echoerr v:errmsg'
1508 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1512 let one = s:repo().translate(':1:'.path)
1513 let two = s:repo().translate(':2:'.path)
1514 let three = s:repo().translate(':3:'.path)
1515 for nr in range(1,bufnr('$'))
1516 let name = fnamemodify(bufname(nr), ':p')
1517 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1518 execute nr.'bdelete'
1523 let zero = s:repo().translate(':0:'.path)
1524 for tab in range(1,tabpagenr('$'))
1525 for winnr in range(1,tabpagewinnr(tab,'$'))
1526 let bufnr = tabpagebuflist(tab)[winnr-1]
1527 let bufname = fnamemodify(bufname(bufnr), ':p')
1528 if bufname ==# zero && bufnr != mybufnr
1529 execute 'tabnext '.tab
1531 execute winnr.'wincmd w'
1532 let restorewinnr = 1
1535 let lnum = line('.')
1536 let last = line('$')
1537 silent execute '$read '.s:fnameescape(file)
1538 silent execute '1,'.last.'delete_'
1543 if exists('restorewinnr')
1546 execute 'tabnext '.mytab
1552 call fugitive#reload_status()
1556 function! s:Wq(force,...) abort
1557 let bang = a:force ? '!' : ''
1558 if exists('b:fugitive_commit_arguments')
1561 let result = call(s:function('s:Write'),[a:force]+a:000)
1562 if result =~# '^\%(write\|wq\|echoerr\)'
1563 return s:sub(result,'^write','wq')
1565 return result.'|quit'.bang
1569 augroup fugitive_commit
1571 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1574 " Section: Gpush, Gfetch
1576 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
1577 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1579 function! s:Dispatch(bang, args)
1580 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1582 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1584 let b:current_compiler = 'git'
1585 let &l:errorformat = s:common_efm
1586 let &l:makeprg = g:fugitive_git_executable . ' ' . a:args
1587 execute cd fnameescape(s:repo().tree())
1588 if exists(':Make') == 2
1591 silent noautocmd make!
1593 return 'call fugitive#cwindow()'
1597 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1598 if empty(cc) | unlet! b:current_compiler | endif
1599 execute cd fnameescape(cwd)
1605 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<f-args>)")
1606 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1607 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<f-args>)")
1609 augroup fugitive_diff
1611 autocmd BufWinLeave *
1612 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1613 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1615 autocmd BufWinEnter *
1616 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1617 \ call s:diffoff() |
1621 function! s:can_diffoff(buf) abort
1622 return getwinvar(bufwinnr(a:buf), '&diff') &&
1623 \ !empty(getbufvar(a:buf, 'git_dir')) &&
1624 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1627 function! fugitive#can_diffoff(buf) abort
1628 return s:can_diffoff(a:buf)
1631 function! s:diff_modifier(count) abort
1632 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1633 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1635 elseif &diffopt =~# 'vertical'
1636 return 'keepalt vert '
1637 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1640 return 'keepalt vert '
1644 function! s:diff_window_count() abort
1646 for nr in range(1,winnr('$'))
1647 let c += getwinvar(nr,'&diff')
1652 function! s:diff_restore() abort
1653 let restore = 'setlocal nodiff noscrollbind'
1654 \ . ' scrollopt=' . &l:scrollopt
1655 \ . (&l:wrap ? ' wrap' : ' nowrap')
1656 \ . ' foldlevel=999'
1657 \ . ' foldmethod=' . &l:foldmethod
1658 \ . ' foldcolumn=' . &l:foldcolumn
1659 \ . ' foldlevel=' . &l:foldlevel
1660 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1661 if has('cursorbind')
1662 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1667 function! s:diffthis() abort
1669 let w:fugitive_diff_restore = s:diff_restore()
1674 function! s:diffoff() abort
1675 if exists('w:fugitive_diff_restore')
1676 execute w:fugitive_diff_restore
1677 unlet w:fugitive_diff_restore
1683 function! s:diffoff_all(dir) abort
1684 let curwin = winnr()
1685 for nr in range(1,winnr('$'))
1686 if getwinvar(nr,'&diff')
1688 execute nr.'wincmd w'
1689 let restorewinnr = 1
1691 if exists('b:git_dir') && b:git_dir ==# a:dir
1696 execute curwin.'wincmd w'
1699 function! s:buffer_compare_age(commit) dict abort
1700 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1701 let my_score = get(scores,':'.self.commit(),0)
1702 let their_score = get(scores,':'.a:commit,0)
1703 if my_score || their_score
1704 return my_score < their_score ? -1 : my_score != their_score
1705 elseif self.commit() ==# a:commit
1708 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1709 if base ==# self.commit()
1711 elseif base ==# a:commit
1714 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1715 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1716 return my_time < their_time ? -1 : my_time != their_time
1719 call s:add_methods('buffer',['compare_age'])
1721 function! s:Diff(vert,...) abort
1722 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1723 if exists(':DiffGitCached')
1724 return 'DiffGitCached'
1725 elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1726 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1728 execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1729 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1732 execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1733 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1739 let arg = join(a:000, ' ')
1743 let file = s:buffer().path('/')
1745 let file = s:buffer().path(':0:')
1746 elseif arg =~# '^:/.'
1748 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1750 return 'echoerr v:errmsg'
1753 let file = s:buffer().expand(arg)
1755 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1756 let file = file.s:buffer().path(':')
1759 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1762 let spec = s:repo().translate(file)
1763 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1764 let restore = s:diff_restore()
1765 if exists('+cursorbind')
1768 let w:fugitive_diff_restore = restore
1769 if s:buffer().compare_age(commit) < 0
1770 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1772 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1774 let w:fugitive_diff_restore = restore
1776 if getwinvar('#', '&diff')
1778 call feedkeys(winnr."\<C-W>w", 'n')
1782 return 'echoerr v:errmsg'
1786 " Section: Gmove, Gremove
1788 function! s:Move(force,destination) abort
1789 if a:destination =~# '^/'
1790 let destination = a:destination[1:-1]
1792 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1793 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1794 let destination = destination[strlen(s:repo().tree('')):-1]
1797 if isdirectory(s:buffer().spec())
1798 " Work around Vim parser idiosyncrasy
1799 let discarded = s:buffer().setvar('&swapfile',0)
1801 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1803 let v:errmsg = 'fugitive: '.message
1804 return 'echoerr v:errmsg'
1806 let destination = s:repo().tree(destination)
1807 if isdirectory(destination)
1808 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1810 call fugitive#reload_status()
1811 if s:buffer().commit() == ''
1812 if isdirectory(destination)
1813 return 'keepalt edit '.s:fnameescape(destination)
1815 return 'keepalt saveas! '.s:fnameescape(destination)
1818 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1822 function! s:MoveComplete(A,L,P) abort
1824 return s:repo().superglob(a:A)
1826 let matches = split(glob(a:A.'*'),"\n")
1827 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1832 function! s:Remove(force) abort
1833 if s:buffer().commit() ==# ''
1835 elseif s:buffer().commit() ==# '0'
1836 let cmd = ['rm','--cached']
1838 let v:errmsg = 'fugitive: rm not supported here'
1839 return 'echoerr v:errmsg'
1842 let cmd += ['--force']
1844 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1846 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1847 return 'echoerr '.string(v:errmsg)
1849 call fugitive#reload_status()
1850 return 'edit'.(a:force ? '!' : '')
1854 augroup fugitive_remove
1856 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1857 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1858 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1864 augroup fugitive_blame
1866 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1867 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1868 autocmd Syntax fugitiveblame call s:BlameSyntax()
1869 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
1870 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
1873 function! s:linechars(pattern) abort
1874 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1875 if exists('*synconcealed') && &conceallevel > 1
1876 for col in range(1, chars)
1877 let chars -= synconcealed(line('.'), col)[0]
1883 function! s:Blame(bang,line1,line2,count,args) abort
1884 if exists('b:fugitive_blamed_bufnr')
1888 if s:buffer().path() == ''
1889 call s:throw('file or blob required')
1891 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1892 call s:throw('unsupported option')
1894 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1895 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1896 if s:buffer().commit() =~# '\D\|..'
1897 let cmd += [s:buffer().commit()]
1899 let cmd += ['--contents', '-']
1901 let cmd += ['--', s:buffer().path()]
1902 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1904 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1907 execute cd.'`=s:repo().tree()`'
1910 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1912 let error = resolve(tempname())
1913 let temp = error.'.fugitiveblame'
1915 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1917 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1924 call s:throw(join(readfile(error),"\n"))
1926 for winnr in range(winnr('$'),1,-1)
1927 call setwinvar(winnr, '&scrollbind', 0)
1928 if exists('+cursorbind')
1929 call setwinvar(winnr, '&cursorbind', 0)
1931 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1932 execute winbufnr(winnr).'bdelete'
1935 let bufnr = bufnr('')
1936 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1937 if exists('+cursorbind')
1938 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
1941 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1944 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1946 setlocal scrollbind nowrap nofoldenable
1947 if exists('+cursorbind')
1950 let top = line('w0') + &scrolloff
1951 let current = line('.')
1952 let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1953 exe 'keepalt leftabove vsplit '.temp
1954 let b:fugitive_blamed_bufnr = bufnr
1955 let w:fugitive_leave = restore
1956 let b:fugitive_blame_arguments = join(a:args,' ')
1960 if exists('+cursorbind')
1963 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1964 if exists('+concealcursor')
1965 setlocal concealcursor=nc conceallevel=2
1967 if exists('+relativenumber')
1968 setlocal norelativenumber
1970 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1971 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1972 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
1973 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1974 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>
1975 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1976 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
1977 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1978 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1979 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1980 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1981 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1982 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1983 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1984 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1995 return 'echoerr v:errmsg'
1999 function! s:BlameCommit(cmd) abort
2000 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
2001 if cmd =~# '^echoerr'
2004 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2005 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2007 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2010 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2012 let head = line('.')
2013 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2014 let top = +matchstr(getline('.'),' +\zs\d\+')
2015 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2016 if lnum >= top && lnum <= top + len
2017 let offset = lnum - top
2025 while offset > 0 && line('.') < line('$')
2027 if getline('.') =~# '^[ +]'
2040 function! s:BlameJump(suffix) abort
2041 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2042 if commit =~# '^0\+$'
2045 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2046 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2048 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2050 let args = b:fugitive_blame_arguments
2051 let offset = line('.') - line('w0')
2052 let bufnr = bufnr('%')
2053 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2055 exe winnr.'wincmd w'
2057 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2062 execute 'Gblame '.args
2064 let delta = line('.') - line('w0') - offset
2066 execute 'normal! '.delta."\<C-E>"
2068 execute 'normal! '.(-delta)."\<C-Y>"
2074 let s:hash_colors = {}
2076 function! s:BlameSyntax() abort
2077 let b:current_syntax = 'fugitiveblame'
2078 let conceal = has('conceal') ? ' conceal' : ''
2079 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2080 syn match FugitiveblameBoundary "^\^"
2081 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2082 syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2083 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2084 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2085 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2086 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2087 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2088 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2089 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2090 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2091 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2092 hi def link FugitiveblameBoundary Keyword
2093 hi def link FugitiveblameHash Identifier
2094 hi def link FugitiveblameUncommitted Ignore
2095 hi def link FugitiveblameTime PreProc
2096 hi def link FugitiveblameLineNumber Number
2097 hi def link FugitiveblameOriginalFile String
2098 hi def link FugitiveblameOriginalLineNumber Float
2099 hi def link FugitiveblameShort FugitiveblameDelimiter
2100 hi def link FugitiveblameDelimiter Delimiter
2101 hi def link FugitiveblameNotCommittedYet Comment
2103 for lnum in range(1, line('$'))
2104 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2105 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2109 if &t_Co > 16 && exists('g:CSApprox_loaded')
2110 \ && empty(get(s:hash_colors, hash))
2111 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2112 let color = csapprox#per_component#Approximate(r, g, b)
2113 if color == 16 && &background ==# 'dark'
2116 let s:hash_colors[hash] = ' ctermfg='.color
2118 let s:hash_colors[hash] = ''
2120 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2122 call s:RehighlightBlame()
2125 function! s:RehighlightBlame() abort
2126 for [hash, cterm] in items(s:hash_colors)
2127 if !empty(cterm) || has('gui_running')
2128 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2130 exe 'hi link FugitiveblameHash'.hash.' Identifier'
2137 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2139 function! s:Browse(bang,line1,count,...) abort
2141 let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
2143 let expanded = s:buffer().rev()
2145 let expanded = s:buffer().path('/')
2147 let expanded = s:buffer().expand(rev)
2149 let full = s:repo().translate(expanded)
2151 if full =~# '^fugitive://'
2152 let commit = matchstr(full,'://.*//\zs\w\+')
2153 let path = matchstr(full,'://.*//\w\+\zs/.*')
2155 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2159 let path = path[1:-1]
2160 elseif s:repo().bare()
2161 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2164 let path = full[strlen(s:repo().tree())+1:-1]
2165 if path =~# '^\.git/'
2167 elseif isdirectory(full)
2173 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2174 let body = readfile(s:repo().dir(path[5:-1]))[0]
2175 if body =~# '^\x\{40\}$'
2179 elseif body =~# '^ref: refs/'
2180 let path = '.git/' . matchstr(body,'ref: \zs.*')
2184 if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
2185 let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
2186 elseif path =~# '^\.git/refs/remotes/.'
2187 let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
2189 let remote = 'origin'
2190 let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
2191 if branch ==# '' && path =~# '^\.git/refs/\w\+/'
2192 let branch = s:sub(path,'^\.git/refs/\w+/','')
2194 if filereadable(s:repo().dir('refs/remotes/'.branch))
2195 let remote = matchstr(branch,'[^/]\+')
2196 let rev = rev[strlen(remote)+1:-1]
2199 let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
2202 let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
2203 if remote =~# '^\.\=$'
2204 let remote = 'origin'
2205 elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
2206 let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
2212 let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
2217 for Handler in g:fugitive_browse_handlers
2218 let url = call(Handler, [{
2225 \ 'line1': a:count > 0 ? a:line1 : 0,
2226 \ 'line2': a:count > 0 ? a:count : 0}])
2233 call s:throw("Instaweb failed to start and '".remote."' is not a supported remote")
2240 return 'echomsg '.string(url)
2241 elseif exists(':Browse') == 2
2242 return 'echomsg '.string(url).'|Browse '.url
2244 if !exists('g:loaded_netrw')
2245 runtime! autoload/netrw.vim
2247 if exists('*netrw#BrowseX')
2248 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
2250 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2254 return 'echoerr v:errmsg'
2258 function! s:github_url(opts, ...) abort
2259 if a:0 || type(a:opts) != type({})
2262 let domain_pattern = 'github\.com'
2263 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2264 for domain in domains
2265 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2267 let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2271 let path = a:opts.path
2272 if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
2273 let root = 'http://' . s:sub(repo,':','/')
2275 let root = 'https://' . s:sub(repo,':','/')
2277 if path =~# '^\.git/refs/heads/'
2278 let branch = a:opts.repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
2280 return root . '/commits/' . path[16:-1]
2282 return root . '/commits/' . branch
2284 elseif path =~# '^\.git/refs/.'
2285 return root . '/commits/' . matchstr(path,'[^/]\+$')
2286 elseif path =~# '.git/\%(config$\|hooks\>\)'
2287 return root . '/admin'
2288 elseif path =~# '^\.git\>'
2291 if a:opts.revision =~# '^[[:alnum:]._-]\+:'
2292 let commit = matchstr(a:opts.revision,'^[^:]*')
2293 elseif a:opts.commit =~# '^\d\=$'
2294 let local = matchstr(a:opts.repo.head_ref(),'\<refs/heads/\zs.*')
2295 let commit = a:opts.repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2300 let commit = a:opts.commit
2302 if a:opts.type == 'tree'
2303 let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2304 elseif a:opts.type == 'blob'
2305 let url = root . '/blob/' . commit . '/' . path
2306 if get(a:opts, 'line2') && a:opts.line1 == a:opts.line2
2307 let url .= '#L' . a:opts.line1
2308 elseif get(a:opts, 'line2')
2309 let url .= '#L' . a:opts.line1 . '-' . a:opts.line2
2311 elseif a:opts.type == 'tag'
2312 let commit = matchstr(getline(3),'^tag \zs.*')
2313 let url = root . '/tree/' . commit
2315 let url = root . '/commit/' . commit
2320 function! s:instaweb_url(opts) abort
2321 let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2322 if output =~# 'http://'
2323 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2327 if a:opts.path =~# '^\.git/refs/.'
2328 return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2329 elseif a:opts.path =~# '^\.git\>'
2333 if a:opts.commit =~# '^\x\{40\}$'
2334 if a:opts.type ==# 'commit'
2335 let url .= ';a=commit'
2337 let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2339 if a:opts.type ==# 'blob'
2340 let tmp = tempname()
2341 silent execute 'write !'.a:opts.repo.git_command('hash-object','-w','--stdin').' > '.tmp
2342 let url .= ';h=' . readfile(tmp)[0]
2345 let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2347 call s:throw('fugitive: cannot browse uncommitted file')
2350 let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2352 if a:opts.path !=# ''
2353 let url .= ';f=' . a:opts.path
2355 if get(a:opts, 'line1')
2356 let url .= '#l' . a:opts.line1
2361 if !exists('g:fugitive_browse_handlers')
2362 let g:fugitive_browse_handlers = []
2365 call extend(g:fugitive_browse_handlers,
2366 \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2368 " Section: File access
2370 function! s:ReplaceCmd(cmd,...) abort
2371 let fn = expand('%:p')
2372 let tmp = tempname()
2377 let old_index = $GIT_INDEX_FILE
2378 let $GIT_INDEX_FILE = a:1
2380 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2384 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2385 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2387 call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2390 if exists('old_index')
2391 let $GIT_INDEX_FILE = old_index
2394 silent exe 'keepalt file '.tmp
2399 silent exe 'keepalt file '.s:fnameescape(fn)
2400 catch /^Vim\%((\a\+)\)\=:E302/
2403 if fnamemodify(bufname('$'), ':p') ==# tmp
2404 silent execute 'bwipeout '.bufnr('$')
2406 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2410 function! s:BufReadIndex() abort
2411 if !exists('b:fugitive_display_format')
2412 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2414 let b:fugitive_display_format = b:fugitive_display_format % 2
2415 let b:fugitive_type = 'index'
2417 let b:git_dir = s:repo().dir()
2418 setlocal noro ma nomodeline
2419 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2422 let index = expand('%:p')
2424 if b:fugitive_display_format
2425 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2428 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2430 if fugitive#git_version() =~# '^0\|^1\.[1-3]\.'
2431 let cmd = s:repo().git_command('status')
2432 elseif fugitive#git_version() =~# '^1\.[4-7]\.'
2433 let cmd = s:repo().git_command('status', '-u')
2435 let cmd = s:repo().git_command(
2436 \ '-c', 'status.displayCommentPrefix=true',
2437 \ '-c', 'color.status=false',
2438 \ '-c', 'status.short=false',
2439 \ '-c', 'status.showUntrackedFiles=all',
2443 execute cd.'`=s:repo().tree()`'
2444 call s:ReplaceCmd(cmd, index)
2449 set foldtext=fugitive#foldtext()
2451 setlocal ro noma nomod noswapfile
2452 if &bufhidden ==# ''
2453 setlocal bufhidden=delete
2458 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2459 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2460 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2461 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2462 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2463 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2464 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2465 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2466 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2467 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2468 nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2469 nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2470 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2471 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2472 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2473 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2474 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2475 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2476 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2477 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2478 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2479 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2480 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2481 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2482 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2483 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2485 return 'echoerr v:errmsg'
2489 function! s:FileRead() abort
2491 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2492 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2493 let hash = repo.rev_parse(path)
2497 let type = repo.git_chomp('cat-file','-t',hash)
2499 " TODO: use count, if possible
2500 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2502 return 'echoerr v:errmsg'
2506 function! s:BufReadIndexFile() abort
2508 let b:fugitive_type = 'blob'
2509 let b:git_dir = s:repo().dir()
2511 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2513 if &bufhidden ==# ''
2514 setlocal bufhidden=delete
2519 catch /^fugitive: rev-parse/
2520 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2523 return 'echoerr v:errmsg'
2527 function! s:BufWriteIndexFile() abort
2528 let tmp = tempname()
2530 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2531 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2532 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2533 let sha1 = readfile(tmp)[0]
2534 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2536 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2538 let info = old_mode.' '.sha1.' '.stage."\t".path
2539 call writefile([info],tmp)
2541 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2543 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2545 if v:shell_error == 0
2547 if exists('#BufWritePost')
2548 execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2550 call fugitive#reload_status()
2553 return 'echoerr '.string('fugitive: '.error)
2560 function! s:BufReadObject() abort
2563 let b:git_dir = s:repo().dir()
2564 let hash = s:buffer().sha1()
2565 if !exists("b:fugitive_type")
2566 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2568 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2569 return "echoerr 'fugitive: unrecognized git type'"
2571 let firstline = getline('.')
2572 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2573 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2576 if b:fugitive_type !=# 'blob'
2580 let pos = getpos('.')
2581 silent keepjumps %delete_
2585 if b:fugitive_type ==# 'tree'
2586 let b:fugitive_display_format = b:fugitive_display_format % 2
2587 if b:fugitive_display_format
2588 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2590 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2592 elseif b:fugitive_type ==# 'tag'
2593 let b:fugitive_display_format = b:fugitive_display_format % 2
2594 if b:fugitive_display_format
2595 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2597 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2599 elseif b:fugitive_type ==# 'commit'
2600 let b:fugitive_display_format = b:fugitive_display_format % 2
2601 if b:fugitive_display_format
2602 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2604 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))
2605 keepjumps call search('^parent ')
2606 if getline('.') ==# 'parent '
2607 silent keepjumps delete_
2609 silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2611 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2613 silent keepjumps delete_
2617 elseif b:fugitive_type ==# 'blob'
2618 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2622 keepjumps call setpos('.',pos)
2623 setlocal ro noma nomod noswapfile
2624 if &bufhidden ==# ''
2625 setlocal bufhidden=delete
2627 if b:fugitive_type !=# 'blob'
2628 setlocal filetype=git foldmethod=syntax
2629 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2630 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2638 return 'echoerr v:errmsg'
2642 augroup fugitive_files
2644 autocmd BufReadCmd index{,.lock}
2645 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2646 \ exe s:BufReadIndex() |
2647 \ elseif filereadable(expand('<amatch>')) |
2651 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2652 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2653 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2654 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2655 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2656 autocmd FileType git
2657 \ if exists('b:git_dir') |
2658 \ call s:JumpInit() |
2662 " Section: Temp files
2664 if !exists('s:temp_files')
2665 let s:temp_files = {}
2668 augroup fugitive_temp
2670 autocmd BufNewFile,BufReadPost *
2671 \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2672 \ let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2673 \ let b:git_type = 'temp' |
2674 \ let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2675 \ call fugitive#detect(expand('<afile>:p')) |
2676 \ setlocal bufhidden=delete nobuflisted |
2677 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2681 " Section: Go to file
2683 function! s:JumpInit() abort
2684 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2686 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2687 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2688 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2689 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>
2690 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2691 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2692 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2693 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2694 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2695 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2696 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2697 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2698 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2702 function! s:GF(mode) abort
2704 let buffer = s:buffer()
2705 let myhash = buffer.sha1()
2706 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2707 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2710 if buffer.type('tree')
2711 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2712 if showtree && line('.') == 1
2714 elseif showtree && line('.') > 2
2715 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2716 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2717 return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2720 elseif buffer.type('blob')
2721 let ref = expand("<cfile>")
2723 let sha1 = buffer.repo().rev_parse(ref)
2727 return s:Edit(a:mode,0,ref)
2733 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2734 let ref = matchstr(getline('.'),'\x\{40\}')
2735 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2736 return s:Edit(a:mode,0,file)
2738 elseif getline('.') =~# '^#\trenamed:.* -> '
2739 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2740 return s:Edit(a:mode,0,file)
2741 elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2742 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2743 return s:Edit(a:mode,0,file)
2744 elseif getline('.') =~# '^#\t.'
2745 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2746 return s:Edit(a:mode,0,file)
2747 elseif getline('.') =~# ': needs merge$'
2748 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2749 return s:Edit(a:mode,0,file).'|Gdiff'
2751 elseif getline('.') ==# '# Not currently on any branch.'
2752 return s:Edit(a:mode,0,'HEAD')
2753 elseif getline('.') =~# '^# On branch '
2754 let file = 'refs/heads/'.getline('.')[12:]
2755 return s:Edit(a:mode,0,file)
2756 elseif getline('.') =~# "^# Your branch .*'"
2757 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2758 return s:Edit(a:mode,0,file)
2761 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2763 if getline('.') =~# '^ref: '
2764 let ref = strpart(getline('.'),5)
2766 elseif getline('.') =~# '^commit \x\{40\}\>'
2767 let ref = matchstr(getline('.'),'\x\{40\}')
2768 return s:Edit(a:mode,0,ref)
2770 elseif getline('.') =~# '^parent \x\{40\}\>'
2771 let ref = matchstr(getline('.'),'\x\{40\}')
2772 let line = line('.')
2774 while getline(line) =~# '^parent '
2778 return s:Edit(a:mode,0,ref)
2780 elseif getline('.') =~ '^tree \x\{40\}$'
2781 let ref = matchstr(getline('.'),'\x\{40\}')
2782 if s:repo().rev_parse(myhash.':') == ref
2783 let ref = myhash.':'
2785 return s:Edit(a:mode,0,ref)
2787 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2788 let ref = matchstr(getline('.'),'\x\{40\}')
2789 let type = matchstr(getline(line('.')+1),'type \zs.*')
2791 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2794 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2795 let ref = matchstr(getline('.'),'\x\{40\}')
2796 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2798 elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2799 let ref = getline('.')[4:]
2801 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2802 let type = getline('.')[0]
2803 let lnum = line('.') - 1
2805 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2806 if getline(lnum) =~# '^[ '.type.']'
2811 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2812 let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2813 let dcmd = '+'.offset.'|normal! zv'
2816 elseif getline('.') =~# '^rename from '
2817 let ref = 'a/'.getline('.')[12:]
2818 elseif getline('.') =~# '^rename to '
2819 let ref = 'b/'.getline('.')[10:]
2821 elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2822 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2823 let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2826 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2827 let line = getline(line('.')-1)
2828 let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2829 let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2832 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2833 let ref = getline('.')
2835 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
2836 return s:Edit(a:mode,0,expand('<cword>'))
2843 let ref = s:sub(ref,'^a/','HEAD:')
2844 let ref = s:sub(ref,'^b/',':0:')
2846 let dref = s:sub(dref,'^a/','HEAD:')
2849 let ref = s:sub(ref,'^a/',myhash.'^:')
2850 let ref = s:sub(ref,'^b/',myhash.':')
2852 let dref = s:sub(dref,'^a/',myhash.'^:')
2856 if ref ==# '/dev/null'
2858 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2862 return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2864 return s:Edit(a:mode,0,ref)
2870 return 'echoerr v:errmsg'
2874 " Section: Statusline
2876 function! s:repo_head_ref() dict abort
2877 if !filereadable(self.dir('HEAD'))
2880 return readfile(self.dir('HEAD'))[0]
2883 call s:add_methods('repo',['head_ref'])
2885 function! fugitive#statusline(...) abort
2886 if !exists('b:git_dir')
2890 if s:buffer().commit() != ''
2891 let status .= ':' . s:buffer().commit()[0:7]
2893 let status .= '('.fugitive#head(7).')'
2894 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2895 return ',GIT'.status
2897 return '[Git'.status.']'
2901 function! fugitive#head(...) abort
2902 if !exists('b:git_dir')
2906 return s:repo().head(a:0 ? a:1 : 0)
2909 augroup fugitive_statusline
2911 autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
2916 function! fugitive#foldtext() abort
2917 if &foldmethod !=# 'syntax'
2919 elseif getline(v:foldstart) =~# '^diff '
2920 let [add, remove] = [-1, -1]
2922 for lnum in range(v:foldstart, v:foldend)
2923 if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2924 let filename = getline(lnum)[6:-1]
2926 if getline(lnum) =~# '^+'
2928 elseif getline(lnum) =~# '^-'
2930 elseif getline(lnum) =~# '^Binary '
2935 let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2938 let filename = getline(v:foldstart)[5:-1]
2941 return 'Binary: '.filename
2943 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2945 elseif getline(v:foldstart) =~# '^# .*:$'
2946 let lines = getline(v:foldstart, v:foldend)
2947 call filter(lines, 'v:val =~# "^#\t"')
2948 cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2949 cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2950 return getline(v:foldstart).' '.join(lines, ', ')
2955 augroup fugitive_foldtext
2957 autocmd User Fugitive
2958 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2959 \ set foldtext=fugitive#foldtext() |